diff --git a/API.md b/API.md index e6d336a2..152e2a88 100644 --- a/API.md +++ b/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 diff --git a/Makefile b/Makefile index 5b74f276..9e2d1c3c 100644 --- a/Makefile +++ b/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 diff --git a/PORTING.md b/PORTING.md index 1d2e1bef..84b4622b 100644 --- a/PORTING.md +++ b/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//`; `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 + [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 + `!` 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. diff --git a/design.md b/design.md index 7f30b86f..b8233356 100644 --- a/design.md +++ b/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). diff --git a/examples/httpd.lua b/examples/httpd.lua new file mode 100644 index 00000000..d4a0f30d --- /dev/null +++ b/examples/httpd.lua @@ -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 diff --git a/libs/calogArchive.h b/libs/calogArchive.h index 22431eb1..a0795db1 100644 --- a/libs/calogArchive.h +++ b/libs/calogArchive.h @@ -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 diff --git a/libs/calogDb.h b/libs/calogDb.h index 12a645f7..8da2aac3 100644 --- a/libs/calogDb.h +++ b/libs/calogDb.h @@ -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 diff --git a/libs/calogExport.h b/libs/calogExport.h index c6e033fb..341091be 100644 --- a/libs/calogExport.h +++ b/libs/calogExport.h @@ -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 diff --git a/libs/calogFs.c b/libs/calogFs.c index 33842f61..ea209fdc 100644 --- a/libs/calogFs.c +++ b/libs/calogFs.c @@ -18,6 +18,27 @@ #include #include #include +#ifdef _WIN32 +#include // _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; diff --git a/libs/calogHttp.c b/libs/calogHttp.c index dc852931..51a75c18 100644 --- a/libs/calogHttp.c +++ b/libs/calogHttp.c @@ -21,9 +21,31 @@ #include #include +#include #include #include +// 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 +#include +#undef X509_NAME +#undef X509_EXTENSIONS +#undef PKCS7_ISSUER_AND_SERIAL +#undef PKCS7_SIGNER_INFO +#undef OCSP_REQUEST +#undef OCSP_RESPONSE +#else +#include +#if defined(__APPLE__) && defined(CALOG_MAC_KEYCHAIN_TRUST) +#include +#include +#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) { diff --git a/libs/calogHttpd.c b/libs/calogHttpd.c deleted file mode 100644 index 32a5fbb0..00000000 --- a/libs/calogHttpd.c +++ /dev/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 -#include -#include -#include -#include -#include -#include - -#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); - } -} diff --git a/libs/calogHttpd.h b/libs/calogHttpd.h deleted file mode 100644 index 417865ca..00000000 --- a/libs/calogHttpd.h +++ /dev/null @@ -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 diff --git a/libs/calogKv.h b/libs/calogKv.h index e1213c7a..a176e0a7 100644 --- a/libs/calogKv.h +++ b/libs/calogKv.h @@ -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 diff --git a/libs/calogNet.c b/libs/calogNet.c index d2c144b9..b56b6046 100644 --- a/libs/calogNet.c +++ b/libs/calogNet.c @@ -11,6 +11,7 @@ #include "calogPlatform.h" #include +#include #include #include #include @@ -18,6 +19,9 @@ #include +#include +#include + // 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()); } diff --git a/libs/calogNet.h b/libs/calogNet.h index 09597d20..e183c5b0 100644 --- a/libs/calogNet.h +++ b/libs/calogNet.h @@ -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 diff --git a/libs/calogProc.c b/libs/calogProc.c index f544798a..9715000c 100644 --- a/libs/calogProc.c +++ b/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 #include -#ifndef _WIN32 +#ifdef _WIN32 +#include +#else #include #include #include +#include #include #include #include @@ -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; } diff --git a/libs/calogProc.h b/libs/calogProc.h index bf25327b..4b10f665 100644 --- a/libs/calogProc.h +++ b/libs/calogProc.h @@ -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 diff --git a/libs/calogPubsub.h b/libs/calogPubsub.h index e671b413..5b975e95 100644 --- a/libs/calogPubsub.h +++ b/libs/calogPubsub.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 diff --git a/libs/calogSsh.h b/libs/calogSsh.h index ea3f67bc..2ba3c45a 100644 --- a/libs/calogSsh.h +++ b/libs/calogSsh.h @@ -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 \ No newline at end of file diff --git a/libs/calogTask.h b/libs/calogTask.h index 6f48b996..c79516b9 100644 --- a/libs/calogTask.h +++ b/libs/calogTask.h @@ -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 diff --git a/libs/calogTimer.c b/libs/calogTimer.c index af3263fa..370d2b5e 100644 --- a/libs/calogTimer.c +++ b/libs/calogTimer.c @@ -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; } { diff --git a/libs/calogTimer.h b/libs/calogTimer.h index c824ec9d..f76e5bd3 100644 --- a/libs/calogTimer.h +++ b/libs/calogTimer.h @@ -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 diff --git a/src/calogInternal.h b/src/calogInternal.h index a3a0fb62..a9c3def1 100644 --- a/src/calogInternal.h +++ b/src/calogInternal.h @@ -10,6 +10,33 @@ #include +#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 and never see this. static inline: no link-time duplication. +#include +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.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 diff --git a/src/calogMain.c b/src/calogMain.c index 70935fdc..237ba441 100644 --- a/src/calogMain.c +++ b/src/calogMain.c @@ -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 #include +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include // 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. diff --git a/src/calogPlatform.h b/src/calogPlatform.h index 4ef2e47b..e4c03e9e 100644 --- a/src/calogPlatform.h +++ b/src/calogPlatform.h @@ -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 diff --git a/src/mybasic/mybasicAdapter.c b/src/mybasic/mybasicAdapter.c index 227e5821..f41807c9 100644 --- a/src/mybasic/mybasicAdapter.c +++ b/src/mybasic/mybasicAdapter.c @@ -15,10 +15,11 @@ #include #include -#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); diff --git a/src/mybasic/mybasicAdapter.h b/src/mybasic/mybasicAdapter.h index 3508cfe4..e9535a84 100644 --- a/src/mybasic/mybasicAdapter.h +++ b/src/mybasic/mybasicAdapter.h @@ -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); diff --git a/src/mybasic/mybasicEngine.c b/src/mybasic/mybasicEngine.c index e4c1b47b..9dcd54f5 100644 --- a/src/mybasic/mybasicEngine.c +++ b/src/mybasic/mybasicEngine.c @@ -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; diff --git a/tests/fuzzParser.c b/tests/fuzzParser.c new file mode 100644 index 00000000..b8191c8f --- /dev/null +++ b/tests/fuzzParser.c @@ -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 +#include + +#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; +} diff --git a/tests/testEngineMyBasic.c b/tests/testEngineMyBasic.c index ea2d0596..d7e88a94 100644 --- a/tests/testEngineMyBasic.c +++ b/tests/testEngineMyBasic.c @@ -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); diff --git a/tests/testExport.c b/tests/testExport.c index db22d7b3..76faf1a9 100644 --- a/tests/testExport.c +++ b/tests/testExport.c @@ -6,6 +6,7 @@ #include "calog.h" #include "calogExport.h" +#include "calogInternal.h" // calogExportShutdown: internal, exercised here for post-shutdown behavior #include #include diff --git a/tests/testHttpd.c b/tests/testHttpd.c deleted file mode 100644 index 532670df..00000000 --- a/tests/testHttpd.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include - -#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; -} diff --git a/tests/testHttpdLua.c b/tests/testHttpdLua.c new file mode 100644 index 00000000..b82fea56 --- /dev/null +++ b/tests/testHttpdLua.c @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/tests/testHttps.c b/tests/testHttps.c index 4edf5374..91ff5370 100644 --- a/tests/testHttps.c +++ b/tests/testHttps.c @@ -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 #include #include +#include #include #include #include @@ -23,6 +27,7 @@ #include #include #include +#include #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); diff --git a/tests/testMyBasic.c b/tests/testMyBasic.c index 3ad295a5..a3c5e07d 100644 --- a/tests/testMyBasic.c +++ b/tests/testMyBasic.c @@ -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; } diff --git a/tests/testPolyglot.c b/tests/testPolyglot.c index 940186a7..ed04b5e5 100644 --- a/tests/testPolyglot.c +++ b/tests/testPolyglot.c @@ -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; } diff --git a/tests/testPubsub.c b/tests/testPubsub.c index 41134419..78d43751 100644 --- a/tests/testPubsub.c +++ b/tests/testPubsub.c @@ -13,6 +13,7 @@ #include "calog.h" #include "calogPubsub.h" +#include "calogInternal.h" // calogPubsubShutdown: internal, exercised here for post-shutdown behavior #include #include diff --git a/tests/testSandbox.c b/tests/testSandbox.c index 9b5eee8c..18683024 100644 --- a/tests/testSandbox.c +++ b/tests/testSandbox.c @@ -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); diff --git a/tools/crossArchive.sh b/tools/crossArchive.sh index b90e4323..b1fca5f8 100755 --- a/tools/crossArchive.sh +++ b/tools/crossArchive.sh @@ -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 -- 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 -- 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 -- 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() { # + 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() { # + 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() { # -- 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 diff --git a/tools/crossDeps.sh b/tools/crossDeps.sh new file mode 100755 index 00000000..8e8ddb75 --- /dev/null +++ b/tools/crossDeps.sh @@ -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//. 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 [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 [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() { # + 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 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" <> "$TC" +fi + +echo "== crossDeps: target=$T triple=$TRIPLE zig=$($ZIG version) ==" +echo " wrappers + $TC regenerated" + +# =================================================================================================== +# Per-dependency build functions. Each builds vendor/ 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//{lib,obj} object listings. + local c + mkdir -p "$O/obj" "$O/lib" || return 1 + + # zlib: -DHAVE_UNISTD_H so it includes 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 -B . + 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//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//lib/libmruby.a; we copy that to $O/mruby/. +# The generated include tree (mrbconf.h + mruby/presym.h) stays at +# vendor/mruby/build//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" <.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 ==" diff --git a/tools/crossMacFull.sh b/tools/crossMacFull.sh new file mode 100755 index 00000000..c29aa94a --- /dev/null +++ b/tools/crossMacFull.sh @@ -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// 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 -- 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 -- 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 -- 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 +# ============================================================================================= +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 ==" diff --git a/tools/crossWinFull.sh b/tools/crossWinFull.sh new file mode 100644 index 00000000..1a6d1d86 --- /dev/null +++ b/tools/crossWinFull.sh @@ -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 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" diff --git a/tools/macStubs/CoreFoundation.tbd b/tools/macStubs/CoreFoundation.tbd new file mode 100644 index 00000000..19d8633d --- /dev/null +++ b/tools/macStubs/CoreFoundation.tbd @@ -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 ] +... diff --git a/tools/macStubs/Security.tbd b/tools/macStubs/Security.tbd new file mode 100644 index 00000000..71fb6c46 --- /dev/null +++ b/tools/macStubs/Security.tbd @@ -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 ] +... diff --git a/tools/macStubs/libiconv.tbd b/tools/macStubs/libiconv.tbd new file mode 100644 index 00000000..c85dfa96 --- /dev/null +++ b/tools/macStubs/libiconv.tbd @@ -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 ] +... diff --git a/vendor/ourbasic/ourBasic.c b/vendor/ourbasic/ourBasic.c index f1c28b8b..ae65b2f1 100644 --- a/vendor/ourbasic/ourBasic.c +++ b/vendor/ourbasic/ourBasic.c @@ -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: diff --git a/vendor/ourbasic/ourBasic.h b/vendor/ourbasic/ourBasic.h index e2d317d5..68e179d7 100644 --- a/vendor/ourbasic/ourBasic.h +++ b/vendor/ourbasic/ourBasic.h @@ -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); diff --git a/vendor/tcl/macosx/GNUmakefile b/vendor/tcl/macosx/GNUmakefile new file mode 100644 index 00000000..77886c7e --- /dev/null +++ b/vendor/tcl/macosx/GNUmakefile @@ -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 +# +# 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: + +#------------------------------------------------------------------------------------------------------- diff --git a/vendor/tcl/macosx/README b/vendor/tcl/macosx/README new file mode 100644 index 00000000..c4221e4c --- /dev/null +++ b/vendor/tcl/macosx/README @@ -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/ diff --git a/vendor/tcl/macosx/Tcl-Info.plist.in b/vendor/tcl/macosx/Tcl-Info.plist.in new file mode 100644 index 00000000..f5c6b155 --- /dev/null +++ b/vendor/tcl/macosx/Tcl-Info.plist.in @@ -0,0 +1,36 @@ + + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + @TCL_LIB_FILE@ + CFBundleGetInfoString + 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 + CFBundleIdentifier + com.tcltk.tcllibrary + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Tcl @TCL_VERSION@ + CFBundlePackageType + FMWK + CFBundleShortVersionString + @TCL_VERSION@@TCL_PATCH_LEVEL@ + CFBundleSignature + Tcl + CFBundleVersion + @TCL_VERSION@@TCL_PATCH_LEVEL@ + + diff --git a/vendor/tcl/macosx/Tclsh-Info.plist.in b/vendor/tcl/macosx/Tclsh-Info.plist.in new file mode 100644 index 00000000..ecc7f210 --- /dev/null +++ b/vendor/tcl/macosx/Tclsh-Info.plist.in @@ -0,0 +1,36 @@ + + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + tclsh@TCL_VERSION@ + CFBundleGetInfoString + 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 + CFBundleIdentifier + com.tcltk.tclsh + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + tclsh + CFBundlePackageType + APPL + CFBundleShortVersionString + @TCL_VERSION@@TCL_PATCH_LEVEL@ + CFBundleSignature + TclS + CFBundleVersion + @TCL_VERSION@@TCL_PATCH_LEVEL@ + + diff --git a/vendor/tcl/macosx/configure b/vendor/tcl/macosx/configure new file mode 100755 index 00000000..dfa21697 --- /dev/null +++ b/vendor/tcl/macosx/configure @@ -0,0 +1,12848 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.73 for tcl 9.0. +# +# +# Copyright (C) 1992-1996, 1998-2017, 2020-2026 Free Software Foundation, +# Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # contradicts POSIX and common usage. Disable this. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else case e in #( + e) case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac ;; +esac +fi + + + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. +as_nl=' +' +export as_nl +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi + +# The user is always right. +if ${PATH_SEPARATOR+false} :; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as 'sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +case $# in # (( + 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; + *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; +esac +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed 'exec'. +printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # contradicts POSIX and common usage. Disable this. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else case e in #( + e) case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ) +then : + +else case e in #( + e) exitcode=1; echo positional parameters were not saved. ;; +esac +fi +test x\$exitcode = x0 || exit 1 +blah=\$(echo \$(echo blah)) +test x\"\$blah\" = xblah || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null +then : + as_have_required=yes +else case e in #( + e) as_have_required=no ;; +esac +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null +then : + +else case e in #( + e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : + CONFIG_SHELL=$as_shell as_have_required=yes + if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null +then : + break 2 +fi +fi + done;; + esac + as_found=false +done +IFS=$as_save_IFS +if $as_found +then : + +else case e in #( + e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi ;; +esac +fi + + + if test "x$CONFIG_SHELL" != x +then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +case $# in # (( + 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; + *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; +esac +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed 'exec'. +printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno +then : + printf '%s\n' "$0: This script requires a shell more modern than all" + printf '%s\n' "$0: the shells that I found on your system." + if test ${ZSH_VERSION+y} ; then + printf '%s\n' "$0: In particular, zsh $ZSH_VERSION has bugs and should" + printf '%s\n' "$0: be upgraded to zsh 4.3.4 or later." + else + printf '%s\n' "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi ;; +esac +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else case e in #( + e) as_fn_append () + { + eval $1=\$$1\$2 + } ;; +esac +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else case e in #( + e) as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } ;; +esac +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + printf '%s\n' "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + t clear + :clear + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { printf '%s\n' "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated + +# Sed expression to map a string onto a valid variable name. +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_CONFIG_STATUS= +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='tcl' +PACKAGE_TARNAME='tcl' +PACKAGE_VERSION='9.0' +PACKAGE_STRING='tcl 9.0' +PACKAGE_BUGREPORT='' +PACKAGE_URL='' + +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_STDIO_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_STRING_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_header_c_list= +ac_subst_vars='DLTEST_SUFFIX +DLTEST_LD +EXTRA_TCLSH_LIBS +EXTRA_BUILD_HTML +EXTRA_INSTALL_BINARIES +EXTRA_INSTALL +EXTRA_APP_CC_SWITCHES +EXTRA_CC_SWITCHES +PACKAGE_DIR +HTML_DIR +PRIVATE_INCLUDE_DIR +TCL_LIBRARY +TCL_MODULE_PATH +TCL_PACKAGE_PATH +BUILD_DLTEST +MAKEFILE_SHELL +DTRACE_OBJ +DTRACE_HDR +DTRACE_SRC +INSTALL_TZDATA +TCL_HAS_LONGLONG +TCL_UNSHARED_LIB_SUFFIX +TCL_SHARED_LIB_SUFFIX +TCL_LIB_VERSIONS_OK +TCL_BUILD_LIB_SPEC +LD_LIBRARY_PATH_VAR +TCL_SHARED_BUILD +CFG_TCL_UNSHARED_LIB_SUFFIX +CFG_TCL_SHARED_LIB_SUFFIX +TCL_SRC_DIR +TCL_BUILD_STUB_LIB_PATH +TCL_BUILD_STUB_LIB_SPEC +TCL_INCLUDE_SPEC +TCL_STUB_LIB_PATH +TCL_STUB_LIB_SPEC +TCL_STUB_LIB_FLAG +TCL_STUB_LIB_FILE +TCL_LIB_SPEC +TCL_LIB_FLAG +TCL_LIB_FILE +PKG_CFG_ARGS +TCL_YEAR +TCL_PATCH_LEVEL +TCL_MINOR_VERSION +TCL_MAJOR_VERSION +TCL_VERSION +TCL_BUILDTIME_LIBRARY +INSTALL_MSGS +INSTALL_LIBRARIES +TCL_ZIP_FILE +ZIPFS_BUILD +ZIP_INSTALL_OBJS +ZIP_PROG_VFSSEARCH +ZIP_PROG_OPTIONS +ZIP_PROG +MACHER_PROG +EXEEXT_FOR_BUILD +CC_FOR_BUILD +DTRACE +LDFLAGS_DEFAULT +CFLAGS_DEFAULT +INSTALL_STUB_LIB +DLL_INSTALL_DIR +INSTALL_LIB +MAKE_STUB_LIB +MAKE_LIB +SHLIB_SUFFIX +SHLIB_CFLAGS +SHLIB_LD_LIBS +TK_SHLIB_LD_EXTRAS +TCL_SHLIB_LD_EXTRAS +SHLIB_LD +STLIB_LD +LD_SEARCH_FLAGS +CC_SEARCH_FLAGS +LDFLAGS_OPTIMIZE +LDFLAGS_DEBUG +CFLAGS_NOLTO +CFLAGS_WARNING +CFLAGS_OPTIMIZE +CFLAGS_DEBUG +LDAIX_SRC +PLAT_SRCS +PLAT_OBJS +DL_OBJS +DL_LIBS +TCL_LIBS +LIBOBJS +AR +RANLIB +TOMMATH_INCLUDE +TOMMATH_SRCS +TOMMATH_OBJS +TCL_PC_CFLAGS +TCL_PC_REQUIRES_PRIVATE +ZLIB_INCLUDE +ZLIB_SRCS +ZLIB_OBJS +TCLSH_PROG +SHARED_BUILD +CPP +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +MAN_FLAGS +ECHO_T +ECHO_N +ECHO_C +target_alias +host_alias +build_alias +LIBS +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +runstatedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL +OBJEXT_FOR_BUILD' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_man_symlinks +enable_man_compression +enable_man_suffix +with_encoding +enable_shared +with_system_libtommath +enable_64bit +enable_64bit_vis +enable_rpath +enable_corefoundation +enable_load +enable_symbols +enable_langinfo +enable_dll_unloading +with_tzdata +enable_dtrace +enable_framework +enable_zipfs +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: '$ac_option' +Try '$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: '$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + printf '%s\n' "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + printf '%s\n' "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`printf '%s\n' $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir runstatedir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: '$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +'configure' configures tcl 9.0 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print 'checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for '--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or '..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, 'make install' will install all the files in +'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify +an installation prefix other than '$ac_default_prefix' using '--prefix', +for instance '--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/tcl] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of tcl 9.0:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-man-symlinks use symlinks for the manpages (default: off) + --enable-man-compression=PROG + compress the manpages with PROG (default: off) + --enable-man-suffix=STRING + use STRING as a suffix to manpage file names + (default: no, tcl if enabled without + specifying STRING) + --enable-shared build and link with shared libraries (default: on) + --enable-64bit enable 64bit support (default: off) + --enable-64bit-vis enable 64bit Sparc VIS support (default: off) + --disable-rpath disable rpath support (default: on) + --enable-corefoundation use CoreFoundation API on MacOSX (default: on) + --enable-load allow dynamic loading and "load" command (default: + on) + --enable-symbols build with debugging symbols (default: off) + --enable-langinfo use nl_langinfo if possible to determine encoding at + startup, otherwise use old heuristic (default: on) + --enable-dll-unloading enable the 'unload' command (default: on) + --enable-dtrace build with DTrace support (default: off) + --enable-framework package shared libraries in MacOSX frameworks + (default: off) + --enable-zipfs build with Zipfs support (default: on) + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-encoding encoding for configuration values (default: utf-8) + --with-system-libtommath + use external libtommath (default: true if available, + false otherwise) + --with-tzdata install timezone data (default: autodetect) + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CPP C preprocessor + +Use these variables to override the choices made by 'configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for configure.gnu first; this name is used for a wrapper for + # Metaconfig's "Configure" on case-insensitive file systems. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + printf '%s\n' "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +tcl configure 9.0 +generated by GNU Autoconf 2.73 + +Copyright (C) 2026 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest.beam + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext +then : + ac_retval=0 +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 ;; +esac +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +printf %s "checking for $2... " >&6; } +if eval test \${$3+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + eval "$3=yes" +else case e in #( + e) eval "$3=no" ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +eval ac_res=\$$3 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf '%s\n' "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + } +then : + ac_retval=0 +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 ;; +esac +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + } +then : + ac_retval=0 +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 ;; +esac +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +printf %s "checking for $2... " >&6; } +if eval test \${$3+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (void); below. */ + +#include +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (void); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main (void) +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + eval "$3=yes" +else case e in #( + e) eval "$3=no" ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext ;; +esac +fi +eval ac_res=\$$3 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf '%s\n' "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func + +# ac_fn_c_check_type LINENO TYPE VAR INCLUDES +# ------------------------------------------- +# Tests whether TYPE exists after having included INCLUDES, setting cache +# variable VAR accordingly. +ac_fn_c_check_type () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +printf %s "checking for $2... " >&6; } +if eval test \${$3+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) eval "$3=no" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main (void) +{ +if (sizeof ($2)) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main (void) +{ +if (sizeof (($2))) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + +else case e in #( + e) eval "$3=yes" ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +eval ac_res=\$$3 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf '%s\n' "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_type + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to run conftest.$ac_ext, and return whether this succeeded. Assumes that +# executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; } +then : + ac_retval=0 +else case e in #( + e) printf '%s\n' "$as_me: program exited with status $ac_status" >&5 + printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status ;; +esac +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_check_decl LINENO SYMBOL VAR INCLUDES EXTRA-OPTIONS FLAG-VAR +# ------------------------------------------------------------------ +# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR +# accordingly. Pass EXTRA-OPTIONS to the compiler, using FLAG-VAR. +ac_fn_check_decl () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + as_decl_name=`echo $2|sed 's/ *(.*//'` + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 +printf %s "checking whether $as_decl_name is declared... " >&6; } +if eval test \${$3+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` + eval ac_save_FLAGS=\$$6 + as_fn_append $6 " $5" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main (void) +{ +#ifndef $as_decl_name +#ifdef __cplusplus + (void) $as_decl_use; +#else + (void) $as_decl_name; +#endif +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + eval "$3=yes" +else case e in #( + e) eval "$3=no" ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + eval $6=\$ac_save_FLAGS + ;; +esac +fi +eval ac_res=\$$3 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf '%s\n' "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_check_decl + +# ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES +# ---------------------------------------------------- +# Tries to find if the field MEMBER exists in type AGGR, after including +# INCLUDES, setting cache variable VAR accordingly. +ac_fn_c_check_member () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2.$3" >&5 +printf %s "checking for $2.$3... " >&6; } +if eval test \${$4+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$5 +int +main (void) +{ +static $2 ac_aggr; +if (ac_aggr.$3) +return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + eval "$4=yes" +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$5 +int +main (void) +{ +static $2 ac_aggr; +if (sizeof ac_aggr.$3) +return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + eval "$4=yes" +else case e in #( + e) eval "$4=no" ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +eval ac_res=\$$4 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf '%s\n' "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_member +ac_configure_args_raw= +for ac_arg +do + case $ac_arg in + *\'*) + ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append ac_configure_args_raw " '$ac_arg'" +done + +case $ac_configure_args_raw in + *$as_nl*) + ac_safe_unquote= ;; + *) + ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. + ac_unsafe_a="$ac_unsafe_z#~" + ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" + ac_configure_args_raw=` printf '%s\n' "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; +esac + +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by tcl $as_me 9.0, which was +generated by GNU Autoconf 2.73. Invocation command line was + + $ $0$ac_configure_args_raw + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + printf '%s\n' "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# Dump the cache to stdout. It can be in a pipe (this is a requirement). +ac_cache_dump () +{ + # The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +printf '%s\n' "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # 'set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # 'set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) +} + +# Print debugging info to stdout. +ac_dump_debugging_info () +{ + echo + + printf '%s\n' "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + ac_cache_dump + echo + + printf '%s\n' "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; + esac + printf '%s\n' "$ac_var='$ac_val'" + done | sort + echo + + if test -n "$ac_subst_files"; then + printf '%s\n' "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; + esac + printf '%s\n' "$ac_var='$ac_val'" + done | sort + echo + fi + + if test -s confdefs.h; then + printf '%s\n' "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + printf '%s\n' "$as_me: caught signal $ac_signal" + printf '%s\n' "$as_me: exit $exit_status" +} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. +ac_exit_trap () +{ + exit_status= + # Sanitize IFS. + IFS=" "" $as_nl" + # Save into config.log some information that might help in debugging. + ac_dump_debugging_info >&5 + eval "rm -f $ac_clean_CONFIG_STATUS core *.core core.conftest.*" && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +} + +trap 'ac_exit_trap $?' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +printf '%s\n' "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +printf '%s\n' "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +if test -n "$CONFIG_SITE"; then + ac_site_files="$CONFIG_SITE" +elif test "x$prefix" != xNONE; then + ac_site_files="$prefix/share/config.site $prefix/etc/config.site" +else + ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" +fi + +for ac_site_file in $ac_site_files +do + case $ac_site_file in #( + */*) : + ;; #( + *) : + ac_site_file=./$ac_site_file ;; +esac + if test -f "$ac_site_file" && test -r "$ac_site_file"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +printf '%s\n' "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See 'config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +printf '%s\n' "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +printf '%s\n' "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Test code for whether the C compiler supports C23 (global declarations) +ac_c_conftest_c23_globals=' +/* Does the compiler advertise conformance to C17 or earlier? + Although GCC 14 does not do that, even with -std=gnu23, + it is close enough, and defines __STDC_VERSION == 202000L. */ +#if !defined __STDC_VERSION__ || __STDC_VERSION__ <= 201710L +# error "Compiler advertises conformance to C17 or earlier" +#endif + +// Check alignas. +char alignas (double) c23_aligned_as_double; +char alignas (0) c23_no_special_alignment; +extern char c23_aligned_as_int; +char alignas (0) alignas (int) c23_aligned_as_int; + +// Check alignof. +enum +{ + c23_int_alignment = alignof (int), + c23_int_array_alignment = alignof (int[100]), + c23_char_alignment = alignof (char) +}; +static_assert (0 < -alignof (int), "alignof is signed"); + +int function_with_unnamed_parameter (int) { return 0; } + +void c23_noreturn (); + +/* Test parsing of string and char UTF-8 literals (including hex escapes). + The parens pacify GCC 15. */ +bool use_u8 = (!sizeof u8"\xFF") == (!u8'\''x'\''); + +bool check_that_bool_works = true | false | !nullptr; +#if !true +# error "true does not work in #if" +#endif +#if false +#elifdef __STDC_VERSION__ +#else +# error "#elifdef does not work" +#endif + +#ifndef __has_c_attribute +# error "__has_c_attribute not defined" +#endif + +#ifndef __has_include +# error "__has_include not defined" +#endif + +#define LPAREN() ( +#define FORTY_TWO(x) 42 +#define VA_OPT_TEST(r, x, ...) __VA_OPT__ (FORTY_TWO r x)) +static_assert (VA_OPT_TEST (LPAREN (), 0, <:-) == 42); + +static_assert (0b101010 == 42); +static_assert (0B101010 == 42); +static_assert (0xDEAD'\''BEEF == 3'\''735'\''928'\''559); +static_assert (0.500'\''000'\''000 == 0.5); + +enum unsignedish : unsigned int { uione = 1 }; +static_assert (0 < -uione); + +#include +constexpr nullptr_t null_pointer = nullptr; + +static typeof (1 + 1L) two () { return 2; } +static long int three () { return 3; } +' + +# Test code for whether the C compiler supports C23 (body of main). +ac_c_conftest_c23_main=' + { + label_before_declaration: + int arr[10] = {}; + if (arr[0]) + goto label_before_declaration; + if (!arr[0]) + goto label_at_end_of_block; + label_at_end_of_block: + } + ok |= !null_pointer; + ok |= two != three; +' + +# Test code for whether the C compiler supports C23 (complete). +ac_c_conftest_c23_program="${ac_c_conftest_c23_globals} + +int +main (int, char **) +{ + int ok = 0; + ${ac_c_conftest_c23_main} + return ok; +} +" + +# Test code for whether the C compiler supports C89 (global declarations) +ac_c_conftest_c89_globals=' +/* Do not test the value of __STDC__, because some compilers define it to 0 + or do not define it, while otherwise adequately conforming. */ + +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ +struct buf { int x; }; +struct buf * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (char **p, int i) +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* C89 style stringification. */ +#define noexpand_stringify(a) #a +const char *stringified = noexpand_stringify(arbitrary+token=sequence); + +/* C89 style token pasting. Exercises some of the corner cases that + e.g. old MSVC gets wrong, but not very hard. */ +#define noexpand_concat(a,b) a##b +#define expand_concat(a,b) noexpand_concat(a,b) +extern int vA; +extern int vbee; +#define aye A +#define bee B +int *pvA = &expand_concat(v,aye); +int *pvbee = &noexpand_concat(v,bee); + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not \xHH hex character constants. + These do not provoke an error unfortunately, instead are silently treated + as an "x". The following induces an error, until -std is added to get + proper ANSI mode. Curiously \x00 != x always comes out true, for an + array size at least. It is necessary to write \x00 == 0 to get something + that is true only with -std. */ +int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) '\''x'\'' +int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), + int, int);' + +# Test code for whether the C compiler supports C89 (body of main). +ac_c_conftest_c89_main=' +ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); +' + +# Test code for whether the C compiler supports C99 (global declarations) +ac_c_conftest_c99_globals=' +/* Does the compiler advertise C99 conformance? */ +#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L +# error "Compiler does not advertise C99 conformance" +#endif + +// See if C++-style comments work. + +#include +extern int puts (const char *); +extern int printf (const char *, ...); +extern int dprintf (int, const char *, ...); +extern void *malloc (size_t); +extern void free (void *); + +// Check varargs macros. These examples are taken from C99 6.10.3.5. +// dprintf is used instead of fprintf to avoid needing to declare +// FILE and stderr, and "aND" is used instead of "and" to work around +// GCC bug 40564 which is irrelevant here. +#define debug(...) dprintf (2, __VA_ARGS__) +#define showlist(...) puts (#__VA_ARGS__) +#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) +static void +test_varargs_macros (void) +{ + int x = 1234; + int y = 5678; + debug ("Flag"); + debug ("X = %d\n", x); + showlist (The first, second, aND third items.); + report (x>y, "x is %d but y is %d", x, y); +} + +// Check long long types. +#define BIG64 18446744073709551615ull +#define BIG32 4294967295ul +#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) +#if !BIG_OK + #error "your preprocessor is broken" +#endif +#if BIG_OK +#else + #error "your preprocessor is broken" +#endif +static long long int bignum = -9223372036854775807LL; +static unsigned long long int ubignum = BIG64; + +struct incomplete_array +{ + int datasize; + double data[]; +}; + +struct named_init { + int number; + const wchar_t *name; + double average; +}; + +typedef const char *ccp; + +static inline int +test_restrict (ccp restrict text) +{ + // Iterate through items via the restricted pointer. + // Also check for declarations in for loops. + for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) + continue; + return 0; +} + +// Check varargs and va_copy. +static bool +test_varargs (const char *format, ...) +{ + va_list args; + va_start (args, format); + va_list args_copy; + va_copy (args_copy, args); + + const char *str = ""; + int number = 0; + float fnumber = 0; + + while (*format) + { + switch (*format++) + { + case '\''s'\'': // string + str = va_arg (args_copy, const char *); + break; + case '\''d'\'': // int + number = va_arg (args_copy, int); + break; + case '\''f'\'': // float + fnumber = va_arg (args_copy, double); + break; + default: + break; + } + } + va_end (args_copy); + va_end (args); + + return *str && number && fnumber; +} +' + +# Test code for whether the C compiler supports C99 (body of main). +ac_c_conftest_c99_main=' + // Check bool. + _Bool success = false; + success |= (argc != 0); + + // Check restrict. + if (test_restrict ("String literal") == 0) + success = true; + const char *restrict newvar = "Another string"; + + // Check varargs. + success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); + test_varargs_macros (); + + // Check flexible array members. + static struct incomplete_array *volatile incomplete_array_pointer; + struct incomplete_array *ia = incomplete_array_pointer; + ia->datasize = 10; + for (int i = 0; i < ia->datasize; ++i) + ia->data[i] = i * 1.234; + // Work around memory leak warnings. + free (ia); + + // Check named initializers. + struct named_init ni = { + .number = 34, + .name = L"Test wide string", + .average = 543.34343, + }; + + ni.number = 58; + + // Do not test for VLAs, as some otherwise-conforming compilers lack them. + // C code should instead use __STDC_NO_VLA__; see Autoconf manual. + + // work around unused variable warnings + ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' + || ni.number != 58); +' + +# Test code for whether the C compiler supports C11 (global declarations) +ac_c_conftest_c11_globals=' +/* Does the compiler advertise C11 conformance? */ +#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L +# error "Compiler does not advertise C11 conformance" +#endif + +// Check _Alignas. +char _Alignas (double) aligned_as_double; +char _Alignas (0) no_special_alignment; +extern char aligned_as_int; +char _Alignas (0) _Alignas (int) aligned_as_int; + +// Check _Alignof. +enum +{ + int_alignment = _Alignof (int), + int_array_alignment = _Alignof (int[100]), + char_alignment = _Alignof (char) +}; +_Static_assert (0 < -_Alignof (int), "_Alignof is signed"); + +// Check _Noreturn. +int _Noreturn does_not_return (void) { for (;;) continue; } + +// Check _Static_assert. +struct test_static_assert +{ + int x; + _Static_assert (sizeof (int) <= sizeof (long int), + "_Static_assert does not work in struct"); + long int y; +}; + +// Check UTF-8 literals. +#define u8 syntax error! +char const utf8_literal[] = u8"happens to be ASCII" "another string"; + +// Check duplicate typedefs. +typedef long *long_ptr; +typedef long int *long_ptr; +typedef long_ptr long_ptr; + +// Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. +struct anonymous +{ + union { + struct { int i; int j; }; + struct { int k; long int l; } w; + }; + int m; +} v1; +' + +# Test code for whether the C compiler supports C11 (body of main). +ac_c_conftest_c11_main=' + _Static_assert ((offsetof (struct anonymous, i) + == offsetof (struct anonymous, w.k)), + "Anonymous union alignment botch"); + v1.i = 2; + v1.w.k = 5; + ok |= v1.i != 5; +' + +# Test code for whether the C compiler supports C11 (complete). +ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} +${ac_c_conftest_c99_globals} +${ac_c_conftest_c11_globals} + +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_c_conftest_c89_main} + ${ac_c_conftest_c99_main} + ${ac_c_conftest_c11_main} + return ok; +} +" + +# Test code for whether the C compiler supports C99 (complete). +ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} +${ac_c_conftest_c99_globals} + +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_c_conftest_c89_main} + ${ac_c_conftest_c99_main} + return ok; +} +" + +# Test code for whether the C compiler supports C89 (complete). +ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} + +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_c_conftest_c89_main} + return ok; +} +" + +as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" +as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" +as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" +as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" +as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" +as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" +as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" +as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" +as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" +as_fn_append ac_header_c_list " sys/time.h sys_time_h HAVE_SYS_TIME_H" +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 +printf '%s\n' "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 +printf '%s\n' "$as_me: error: '$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w= + for ac_val in x $ac_old_val; do + ac_old_val_w="$ac_old_val_w $ac_val" + done + ac_new_val_w= + for ac_val in x $ac_new_val; do + ac_new_val_w="$ac_new_val_w $ac_val" + done + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 +printf '%s\n' "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 +printf '%s\n' "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 +printf '%s\n' "$as_me: former value: '$ac_old_val'" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 +printf '%s\n' "$as_me: current value: '$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`printf '%s\n' "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +printf '%s\n' "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' + and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + + +# Determine whether it's possible to make 'echo' print without a newline. +# These variables are no longer used directly by Autoconf, but are AC_SUBSTed +# for compatibility with existing Makefiles. +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + + + ac_config_headers="$ac_config_headers tclConfig.h:../unix/tclConfig.h.in" + + + + + + +TCL_VERSION=9.0 +TCL_MAJOR_VERSION=9 +TCL_MINOR_VERSION=0 +TCL_PATCH_LEVEL=".4" +VERSION=${TCL_VERSION} + +EXTRA_INSTALL_BINARIES=${EXTRA_INSTALL_BINARIES:-"@:"} +EXTRA_BUILD_HTML=${EXTRA_BUILD_HTML:-"@:"} + +#------------------------------------------------------------------------ +# Setup configure arguments for bundled packages +#------------------------------------------------------------------------ + +PKG_CFG_ARGS="$ac_configure_args ${PKG_CFG_ARGS}" + +if test -r "$cache_file" -a -f "$cache_file"; then + case $cache_file in + [\\/]* | ?:[\\/]* ) pkg_cache_file=$cache_file ;; + *) pkg_cache_file=../../$cache_file ;; + esac + PKG_CFG_ARGS="${PKG_CFG_ARGS} --cache-file=$pkg_cache_file" +fi + +#------------------------------------------------------------------------ +# Empty slate for bundled packages, to avoid stale configuration +#------------------------------------------------------------------------ +#rm -Rf pkgs +if test -f Makefile; then + make distclean-packages +fi + +#------------------------------------------------------------------------ +# Handle the --prefix=... option +#------------------------------------------------------------------------ + +if test "${prefix}" = "NONE"; then + prefix=/usr/local +fi +if test "${exec_prefix}" = "NONE"; then + exec_prefix=$prefix +fi +# Make sure srcdir is fully qualified! +srcdir="`cd "$srcdir" ; pwd`" +TCL_SRC_DIR="`cd "$srcdir"/..; pwd`" + +#------------------------------------------------------------------------ +# Compress and/or soft link the manpages? +#------------------------------------------------------------------------ + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to use symlinks for manpages" >&5 +printf %s "checking whether to use symlinks for manpages... " >&6; } + # Check whether --enable-man-symlinks was given. +if test ${enable_man_symlinks+y} +then : + enableval=$enable_man_symlinks; test "$enableval" != "no" && MAN_FLAGS="$MAN_FLAGS --symlinks" +else case e in #( + e) enableval="no" ;; +esac +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $enableval" >&5 +printf '%s\n' "$enableval" >&6; } + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to compress the manpages" >&5 +printf %s "checking whether to compress the manpages... " >&6; } + # Check whether --enable-man-compression was given. +if test ${enable_man_compression+y} +then : + enableval=$enable_man_compression; case $enableval in + yes) as_fn_error $? "missing argument to --enable-man-compression" "$LINENO" 5;; + no) ;; + *) MAN_FLAGS="$MAN_FLAGS --compress $enableval";; + esac +else case e in #( + e) enableval="no" ;; +esac +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $enableval" >&5 +printf '%s\n' "$enableval" >&6; } + if test "$enableval" != "no"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for compressed file suffix" >&5 +printf %s "checking for compressed file suffix... " >&6; } + touch TeST + $enableval TeST + Z=`ls TeST.* | sed 's/^....//'` + rm -f TeST* + MAN_FLAGS="$MAN_FLAGS --extension $Z" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $Z" >&5 +printf '%s\n' "$Z" >&6; } + fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to add a package name suffix for the manpages" >&5 +printf %s "checking whether to add a package name suffix for the manpages... " >&6; } + # Check whether --enable-man-suffix was given. +if test ${enable_man_suffix+y} +then : + enableval=$enable_man_suffix; case $enableval in + yes) enableval="tcl" MAN_FLAGS="$MAN_FLAGS --suffix $enableval";; + no) ;; + *) MAN_FLAGS="$MAN_FLAGS --suffix $enableval";; + esac +else case e in #( + e) enableval="no" ;; +esac +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $enableval" >&5 +printf '%s\n' "$enableval" >&6; } + + + + +#------------------------------------------------------------------------ +# 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_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +printf '%s\n' "$ac_ct_CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" + fi +fi +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +printf '%s\n' "$ac_ct_CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. +set dummy ${ac_tool_prefix}clang; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}clang" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "clang", so it can be a program name with args. +set dummy clang; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="clang" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +printf '%s\n' "$ac_ct_CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +fi + + +test -z "$CC" && { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See 'config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion -version; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +printf %s "checking whether the C compiler works... " >&6; } +ac_link_default=`printf '%s\n' "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. +# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an '-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else case e in #( + e) ac_file='' ;; +esac +fi +if test -z "$ac_file" +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See 'config.log' for more details" "$LINENO" 5; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +printf %s "checking for C compiler default output file name... " >&6; } +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +printf '%s\n' "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +printf %s "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) +# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will +# work properly (i.e., refer to 'conftest.exe'), while it won't with +# 'rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else case e in #( + e) { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See 'config.log' for more details" "$LINENO" 5; } ;; +esac +fi +rm -f conftest conftest$ac_cv_exeext +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +printf '%s\n' "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +FILE *f = fopen ("conftest.out", "w"); + if (!f) + return 1; + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +printf %s "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error 77 "cannot run C compiled programs. +If you meant to cross compile, use '--host'. +See 'config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +printf '%s\n' "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext \ + conftest.o conftest.obj conftest.out +ac_clean_files=$ac_clean_files_save +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +printf %s "checking for suffix of object files... " >&6; } +if test ${ac_cv_objext+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See 'config.log' for more details" "$LINENO" 5; } ;; +esac +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +printf '%s\n' "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 +printf %s "checking whether the compiler supports GNU C... " >&6; } +if test ${ac_cv_c_compiler_gnu+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_compiler_gnu=yes +else case e in #( + e) ac_compiler_gnu=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +printf '%s\n' "$ac_cv_c_compiler_gnu" >&6; } +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+y} +ac_save_CFLAGS=$CFLAGS +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +printf %s "checking whether $CC accepts -g... " >&6; } +if test ${ac_cv_prog_cc_g+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_g=yes +else case e in #( + e) CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + +else case e in #( + e) ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +printf '%s\n' "$ac_cv_prog_cc_g" >&6; } +if test $ac_test_CFLAGS; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +ac_prog_cc_stdc=no +if test x$ac_prog_cc_stdc = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C23 features" >&5 +printf %s "checking for $CC option to enable C23 features... " >&6; } +if test ${ac_cv_prog_cc_c23+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cc_c23=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_c_conftest_c23_program +_ACEOF +for ac_arg in '' -std=gnu23 +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_c23=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cc_c23" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC ;; +esac +fi + +if test "x$ac_cv_prog_cc_c23" = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c23" = x +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c23" >&5 +printf '%s\n' "$ac_cv_prog_cc_c23" >&6; } + CC="$CC $ac_cv_prog_cc_c23" ;; +esac +fi + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c23 + ac_prog_cc_stdc=c23 ;; +esac +fi +fi +if test x$ac_prog_cc_stdc = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 +printf %s "checking for $CC option to enable C11 features... " >&6; } +if test ${ac_cv_prog_cc_c11+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cc_c11=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_c_conftest_c11_program +_ACEOF +for ac_arg in '' -std=gnu11 -std:c11 +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_c11=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cc_c11" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC ;; +esac +fi + +if test "x$ac_cv_prog_cc_c11" = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c11" = x +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 +printf '%s\n' "$ac_cv_prog_cc_c11" >&6; } + CC="$CC $ac_cv_prog_cc_c11" ;; +esac +fi + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 + ac_prog_cc_stdc=c11 ;; +esac +fi +fi +if test x$ac_prog_cc_stdc = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 +printf %s "checking for $CC option to enable C99 features... " >&6; } +if test ${ac_cv_prog_cc_c99+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cc_c99=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_c_conftest_c99_program +_ACEOF +for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_c99=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cc_c99" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC ;; +esac +fi + +if test "x$ac_cv_prog_cc_c99" = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c99" = x +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 +printf '%s\n' "$ac_cv_prog_cc_c99" >&6; } + CC="$CC $ac_cv_prog_cc_c99" ;; +esac +fi + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 + ac_prog_cc_stdc=c99 ;; +esac +fi +fi +if test x$ac_prog_cc_stdc = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 +printf %s "checking for $CC option to enable C89 features... " >&6; } +if test ${ac_cv_prog_cc_c89+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_c_conftest_c89_program +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC ;; +esac +fi + +if test "x$ac_cv_prog_cc_c89" = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c89" = x +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +printf '%s\n' "$ac_cv_prog_cc_c89" >&6; } + CC="$CC $ac_cv_prog_cc_c89" ;; +esac +fi + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 + ac_prog_cc_stdc=c89 ;; +esac +fi +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for inline" >&5 +printf %s "checking for inline... " >&6; } +if test ${ac_cv_c_inline+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_c_inline=no +for ac_kw in inline __inline__ __inline; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifndef __cplusplus +typedef int foo_t; +static $ac_kw foo_t static_foo (void) {return 0; } +$ac_kw foo_t foo (void) {return 0; } +#endif + +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_c_inline=$ac_kw +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + test "$ac_cv_c_inline" != no && break +done + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_inline" >&5 +printf '%s\n' "$ac_cv_c_inline" >&6; } + +case $ac_cv_c_inline in + inline | yes) ;; + *) + case $ac_cv_c_inline in + no) ac_val=;; + *) ac_val=$ac_cv_c_inline;; + esac + cat >>confdefs.h <<_ACEOF +#ifndef __cplusplus +#define inline $ac_val +#endif +_ACEOF + ;; +esac + + + +#-------------------------------------------------------------------- +# Supply substitutes for missing POSIX header files. Special notes: +# - stdlib.h doesn't define strtol or strtoul in some versions +# of SunOS +# - some versions of string.h don't declare procedures such +# as strstr +# Do this early, otherwise an autoconf bug throws errors on configure +#-------------------------------------------------------------------- + +ac_header= ac_cache= +for ac_item in $ac_header_c_list +do + if test $ac_cache; then + ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" + if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then + printf '%s\n' "#define $ac_item 1" >> confdefs.h + fi + ac_header= ac_cache= + elif test $ac_header; then + ac_cache=$ac_item + else + ac_header=$ac_item + fi +done + + + + + + + + +if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes +then : + +printf '%s\n' "#define STDC_HEADERS 1" >>confdefs.h + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +printf %s "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if test ${ac_cv_prog_CPP+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) # Double quotes because $CC needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" cpp /lib/cpp + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO" +then : + +else case e in #( + e) # Broken: fails on valid input. +continue ;; +esac +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO" +then : + # Broken: success on invalid input. +continue +else case e in #( + e) # Passes both tests. +ac_preproc_ok=: +break ;; +esac +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok +then : + break +fi + + done + ac_cv_prog_CPP=$CPP + ;; +esac +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +printf '%s\n' "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO" +then : + +else case e in #( + e) # Broken: fails on valid input. +continue ;; +esac +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO" +then : + # Broken: success on invalid input. +continue +else case e in #( + e) # Passes both tests. +ac_preproc_ok=: +break ;; +esac +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok +then : + +else case e in #( + e) { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See 'config.log' for more details" "$LINENO" 5; } ;; +esac +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for egrep -e" >&5 +printf %s "checking for egrep -e... " >&6; } +if test ${ac_cv_path_EGREP_TRADITIONAL+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -z "$EGREP_TRADITIONAL"; then + ac_path_EGREP_TRADITIONAL_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_prog in grep ggrep + do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue +# Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. + # Check for GNU $ac_path_EGREP_TRADITIONAL +case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( +*GNU*) + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; +#( +*) + ac_count=0 + printf %s 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + printf '%s\n' 'EGREP_TRADITIONAL' >> "conftest.nl" + "$ac_path_EGREP_TRADITIONAL" -E 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" + ac_path_EGREP_TRADITIONAL_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_TRADITIONAL_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then + : + fi +else + ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL +fi + + if test "$ac_cv_path_EGREP_TRADITIONAL" +then : + ac_cv_path_EGREP_TRADITIONAL="$ac_cv_path_EGREP_TRADITIONAL -E" +else case e in #( + e) if test -z "$EGREP_TRADITIONAL"; then + ac_path_EGREP_TRADITIONAL_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_prog in egrep + do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue +# Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. + # Check for GNU $ac_path_EGREP_TRADITIONAL +case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( +*GNU*) + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; +#( +*) + ac_count=0 + printf %s 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + printf '%s\n' 'EGREP_TRADITIONAL' >> "conftest.nl" + "$ac_path_EGREP_TRADITIONAL" 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" + ac_path_EGREP_TRADITIONAL_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_TRADITIONAL_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL +fi + ;; +esac +fi ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP_TRADITIONAL" >&5 +printf '%s\n' "$ac_cv_path_EGREP_TRADITIONAL" >&6; } + EGREP_TRADITIONAL=$ac_cv_path_EGREP_TRADITIONAL + + + ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default" +if test "x$ac_cv_header_string_h" = xyes +then : + tcl_ok=1 +else case e in #( + e) tcl_ok=0 ;; +esac +fi + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP_TRADITIONAL "strstr" >/dev/null 2>&1 +then : + +else case e in #( + e) tcl_ok=0 ;; +esac +fi +rm -rf conftest* + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP_TRADITIONAL "strerror" >/dev/null 2>&1 +then : + +else case e in #( + e) tcl_ok=0 ;; +esac +fi +rm -rf conftest* + + + ac_fn_c_check_header_compile "$LINENO" "sys/wait.h" "ac_cv_header_sys_wait_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_wait_h" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_SYS_WAIT_H 1" >>confdefs.h + ;; +esac +fi + + ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default" +if test "x$ac_cv_header_dlfcn_h" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_DLFCN_H 1" >>confdefs.h + ;; +esac +fi + + + # OS/390 lacks sys/param.h (and doesn't need it, by chance). + ac_fn_c_check_header_compile "$LINENO" "sys/param.h" "ac_cv_header_sys_param_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_param_h" = xyes +then : + printf '%s\n' "#define HAVE_SYS_PARAM_H 1" >>confdefs.h + +fi + + + +#-------------------------------------------------------------------- +# Determines the correct executable file extension (.exe) +#-------------------------------------------------------------------- + + + +#------------------------------------------------------------------------ +# If we're using GCC, see if the compiler understands -pipe. If so, use it. +# It makes compiling go faster. (This is only a performance feature.) +#------------------------------------------------------------------------ + +if test -z "$no_pipe" && test -n "$GCC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if the compiler understands -pipe" >&5 +printf %s "checking if the compiler understands -pipe... " >&6; } +if test ${tcl_cv_cc_pipe+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -pipe" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_cc_pipe=yes +else case e in #( + e) tcl_cv_cc_pipe=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_pipe" >&5 +printf '%s\n' "$tcl_cv_cc_pipe" >&6; } + if test $tcl_cv_cc_pipe = yes; then + CFLAGS="$CFLAGS -pipe" + fi +fi + +#------------------------------------------------------------------------ +# Embedded configuration information, encoding to use for the values, TIP #59 +#------------------------------------------------------------------------ + + + +# Check whether --with-encoding was given. +if test ${with_encoding+y} +then : + withval=$with_encoding; with_tcencoding=${withval} +fi + + + if test x"${with_tcencoding}" != x ; then + +cat >>confdefs.h <<_ACEOF +#define TCL_CFGVAL_ENCODING "${with_tcencoding}" +_ACEOF + + else + +printf '%s\n' "#define TCL_CFGVAL_ENCODING \"utf-8\"" >>confdefs.h + + fi + + +#-------------------------------------------------------------------- +# Look for libraries that we will need when compiling the Tcl shell +#-------------------------------------------------------------------- + + + #-------------------------------------------------------------------- + # On a few very rare systems, all of the libm.a stuff is + # already in libc.a. Set compiler flags accordingly. + #-------------------------------------------------------------------- + + ac_fn_c_check_func "$LINENO" "sin" "ac_cv_func_sin" +if test "x$ac_cv_func_sin" = xyes +then : + MATH_LIBS="" +else case e in #( + e) MATH_LIBS="-lm" ;; +esac +fi + + + #-------------------------------------------------------------------- + # Interactive UNIX requires -linet instead of -lsocket, plus it + # needs net/errno.h to define the socket-related error codes. + #-------------------------------------------------------------------- + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for main in -linet" >&5 +printf %s "checking for main in -linet... " >&6; } +if test ${ac_cv_lib_inet_main+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-linet $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + +int +main (void) +{ +return main (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_inet_main=yes +else case e in #( + e) ac_cv_lib_inet_main=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_inet_main" >&5 +printf '%s\n' "$ac_cv_lib_inet_main" >&6; } +if test "x$ac_cv_lib_inet_main" = xyes +then : + LIBS="$LIBS -linet" +fi + + ac_fn_c_check_header_compile "$LINENO" "net/errno.h" "ac_cv_header_net_errno_h" "$ac_includes_default" +if test "x$ac_cv_header_net_errno_h" = xyes +then : + + +printf '%s\n' "#define HAVE_NET_ERRNO_H 1" >>confdefs.h + +fi + + + #-------------------------------------------------------------------- + # Check for the existence of the -lsocket and -lnsl libraries. + # The order here is important, so that they end up in the right + # order in the command line generated by make. Here are some + # special considerations: + # 1. Use "connect" and "accept" to check for -lsocket, and + # "gethostbyname" to check for -lnsl. + # 2. Use each function name only once: can't redo a check because + # autoconf caches the results of the last check and won't redo it. + # 3. Use -lnsl and -lsocket only if they supply procedures that + # aren't already present in the normal libraries. This is because + # IRIX 5.2 has libraries, but they aren't needed and they're + # bogus: they goof up name resolution if used. + # 4. On some SVR4 systems, can't use -lsocket without -lnsl too. + # To get around this problem, check for both libraries together + # if -lsocket doesn't work by itself. + #-------------------------------------------------------------------- + + tcl_checkBoth=0 + ac_fn_c_check_func "$LINENO" "connect" "ac_cv_func_connect" +if test "x$ac_cv_func_connect" = xyes +then : + tcl_checkSocket=0 +else case e in #( + e) tcl_checkSocket=1 ;; +esac +fi + + if test "$tcl_checkSocket" = 1; then + ac_fn_c_check_func "$LINENO" "setsockopt" "ac_cv_func_setsockopt" +if test "x$ac_cv_func_setsockopt" = xyes +then : + +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for setsockopt in -lsocket" >&5 +printf %s "checking for setsockopt in -lsocket... " >&6; } +if test ${ac_cv_lib_socket_setsockopt+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lsocket $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char setsockopt (void); +int +main (void) +{ +return setsockopt (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_socket_setsockopt=yes +else case e in #( + e) ac_cv_lib_socket_setsockopt=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_setsockopt" >&5 +printf '%s\n' "$ac_cv_lib_socket_setsockopt" >&6; } +if test "x$ac_cv_lib_socket_setsockopt" = xyes +then : + LIBS="$LIBS -lsocket" +else case e in #( + e) tcl_checkBoth=1 ;; +esac +fi + ;; +esac +fi + + fi + if test "$tcl_checkBoth" = 1; then + tk_oldLibs=$LIBS + LIBS="$LIBS -lsocket -lnsl" + ac_fn_c_check_func "$LINENO" "accept" "ac_cv_func_accept" +if test "x$ac_cv_func_accept" = xyes +then : + tcl_checkNsl=0 +else case e in #( + e) LIBS=$tk_oldLibs ;; +esac +fi + + fi + ac_fn_c_check_func "$LINENO" "gethostbyname" "ac_cv_func_gethostbyname" +if test "x$ac_cv_func_gethostbyname" = xyes +then : + +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for gethostbyname in -lnsl" >&5 +printf %s "checking for gethostbyname in -lnsl... " >&6; } +if test ${ac_cv_lib_nsl_gethostbyname+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lnsl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char gethostbyname (void); +int +main (void) +{ +return gethostbyname (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_nsl_gethostbyname=yes +else case e in #( + e) ac_cv_lib_nsl_gethostbyname=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_gethostbyname" >&5 +printf '%s\n' "$ac_cv_lib_nsl_gethostbyname" >&6; } +if test "x$ac_cv_lib_nsl_gethostbyname" = xyes +then : + LIBS="$LIBS -lnsl" +fi + ;; +esac +fi + + + +printf '%s\n' "#define _REENTRANT 1" >>confdefs.h + + +printf '%s\n' "#define _THREAD_SAFE 1" >>confdefs.h + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lpthread" >&5 +printf %s "checking for pthread_mutex_init in -lpthread... " >&6; } +if test ${ac_cv_lib_pthread_pthread_mutex_init+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_mutex_init (void); +int +main (void) +{ +return pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_pthread_pthread_mutex_init=yes +else case e in #( + e) ac_cv_lib_pthread_pthread_mutex_init=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_mutex_init" >&5 +printf '%s\n' "$ac_cv_lib_pthread_pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_pthread_pthread_mutex_init" = xyes +then : + tcl_ok=yes +else case e in #( + e) tcl_ok=no ;; +esac +fi + + if test "$tcl_ok" = "no"; then + # Check a little harder for __pthread_mutex_init in the same + # library, as some systems hide it there until pthread.h is + # defined. We could alternatively do an AC_TRY_COMPILE with + # pthread.h, but that will work with libpthread really doesn't + # exist, like AIX 4.2. [Bug: 4359] + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for __pthread_mutex_init in -lpthread" >&5 +printf %s "checking for __pthread_mutex_init in -lpthread... " >&6; } +if test ${ac_cv_lib_pthread___pthread_mutex_init+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char __pthread_mutex_init (void); +int +main (void) +{ +return __pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_pthread___pthread_mutex_init=yes +else case e in #( + e) ac_cv_lib_pthread___pthread_mutex_init=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread___pthread_mutex_init" >&5 +printf '%s\n' "$ac_cv_lib_pthread___pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_pthread___pthread_mutex_init" = xyes +then : + tcl_ok=yes +else case e in #( + e) tcl_ok=no ;; +esac +fi + + fi + + if test "$tcl_ok" = "yes"; then + # The space is needed + THREADS_LIBS=" -lpthread" + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lpthreads" >&5 +printf %s "checking for pthread_mutex_init in -lpthreads... " >&6; } +if test ${ac_cv_lib_pthreads_pthread_mutex_init+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthreads $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_mutex_init (void); +int +main (void) +{ +return pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_pthreads_pthread_mutex_init=yes +else case e in #( + e) ac_cv_lib_pthreads_pthread_mutex_init=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthreads_pthread_mutex_init" >&5 +printf '%s\n' "$ac_cv_lib_pthreads_pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_pthreads_pthread_mutex_init" = xyes +then : + _ok=yes +else case e in #( + e) tcl_ok=no ;; +esac +fi + + if test "$tcl_ok" = "yes"; then + # The space is needed + THREADS_LIBS=" -lpthreads" + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lc" >&5 +printf %s "checking for pthread_mutex_init in -lc... " >&6; } +if test ${ac_cv_lib_c_pthread_mutex_init+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lc $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_mutex_init (void); +int +main (void) +{ +return pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_c_pthread_mutex_init=yes +else case e in #( + e) ac_cv_lib_c_pthread_mutex_init=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_pthread_mutex_init" >&5 +printf '%s\n' "$ac_cv_lib_c_pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_c_pthread_mutex_init" = xyes +then : + tcl_ok=yes +else case e in #( + e) tcl_ok=no ;; +esac +fi + + if test "$tcl_ok" = "no"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for pthread_mutex_init in -lc_r" >&5 +printf %s "checking for pthread_mutex_init in -lc_r... " >&6; } +if test ${ac_cv_lib_c_r_pthread_mutex_init+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lc_r $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_mutex_init (void); +int +main (void) +{ +return pthread_mutex_init (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_c_r_pthread_mutex_init=yes +else case e in #( + e) ac_cv_lib_c_r_pthread_mutex_init=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_r_pthread_mutex_init" >&5 +printf '%s\n' "$ac_cv_lib_c_r_pthread_mutex_init" >&6; } +if test "x$ac_cv_lib_c_r_pthread_mutex_init" = xyes +then : + tcl_ok=yes +else case e in #( + e) tcl_ok=no ;; +esac +fi + + if test "$tcl_ok" = "yes"; then + # The space is needed + THREADS_LIBS=" -pthread" + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: Don't know how to find pthread lib on your system - you must edit the LIBS in the Makefile..." >&5 +printf '%s\n' "$as_me: WARNING: Don't know how to find pthread lib on your system - you must edit the LIBS in the Makefile..." >&2;} + fi + fi + fi + fi + + # Does the pthread-implementation provide + # 'pthread_attr_setstacksize' ? + + ac_saved_libs=$LIBS + LIBS="$LIBS $THREADS_LIBS" + ac_fn_c_check_func "$LINENO" "pthread_attr_setstacksize" "ac_cv_func_pthread_attr_setstacksize" +if test "x$ac_cv_func_pthread_attr_setstacksize" = xyes +then : + printf '%s\n' "#define HAVE_PTHREAD_ATTR_SETSTACKSIZE 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "pthread_atfork" "ac_cv_func_pthread_atfork" +if test "x$ac_cv_func_pthread_atfork" = xyes +then : + printf '%s\n' "#define HAVE_PTHREAD_ATFORK 1" >>confdefs.h + +fi + + LIBS=$ac_saved_libs + + +# Add the threads support libraries +LIBS="$LIBS$THREADS_LIBS" + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to build libraries" >&5 +printf %s "checking how to build libraries... " >&6; } + # Check whether --enable-shared was given. +if test ${enable_shared+y} +then : + enableval=$enable_shared; tcl_ok=$enableval +else case e in #( + e) tcl_ok=yes ;; +esac +fi + + if test "$tcl_ok" = "yes" ; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: shared" >&5 +printf '%s\n' "shared" >&6; } + SHARED_BUILD=1 + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: static" >&5 +printf '%s\n' "static" >&6; } + SHARED_BUILD=0 + +printf '%s\n' "#define STATIC_BUILD 1" >>confdefs.h + + fi + + + +#-------------------------------------------------------------------- +# Look for a native installed tclsh binary (if available) +# If one cannot be found then use the binary we build (fails for +# cross compiling). This is used for NATIVE_TCLSH in Makefile. +#-------------------------------------------------------------------- + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for tclsh" >&5 +printf %s "checking for tclsh... " >&6; } + if test ${ac_cv_path_tclsh+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/tclsh[8-9]* 2> /dev/null` \ + `ls -r $dir/tclsh* 2> /dev/null` ; do + if test x"$ac_cv_path_tclsh" = x ; then + if test -f "$j" ; then + ac_cv_path_tclsh=$j + break + fi + fi + done + done + ;; +esac +fi + + + if test -f "$ac_cv_path_tclsh" ; then + TCLSH_PROG="$ac_cv_path_tclsh" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $TCLSH_PROG" >&5 +printf '%s\n' "$TCLSH_PROG" >&6; } + else + # It is not an error if an installed version of Tcl can't be located. + TCLSH_PROG="" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: No tclsh found on PATH" >&5 +printf '%s\n' "No tclsh found on PATH" >&6; } + fi + + +if test "$TCLSH_PROG" = ""; then + TCLSH_PROG='./${TCL_EXE}' +fi + +#------------------------------------------------------------------------ +# Add stuff for zlib +#------------------------------------------------------------------------ + +zlib_ok=yes +ac_fn_c_check_header_compile "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default" +if test "x$ac_cv_header_zlib_h" = xyes +then : + + ac_fn_c_check_type "$LINENO" "gz_header" "ac_cv_type_gz_header" "#include +" +if test "x$ac_cv_type_gz_header" = xyes +then : + +else case e in #( + e) zlib_ok=no ;; +esac +fi + +else case e in #( + e) + zlib_ok=no ;; +esac +fi + +if test $zlib_ok = yes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for library containing deflateSetHeader" >&5 +printf %s "checking for library containing deflateSetHeader... " >&6; } +if test ${ac_cv_search_deflateSetHeader+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char deflateSetHeader (void); +int +main (void) +{ +return deflateSetHeader (); + ; + return 0; +} +_ACEOF +for ac_lib in '' z +do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO" +then : + ac_cv_search_deflateSetHeader=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext + if test ${ac_cv_search_deflateSetHeader+y} +then : + break +fi +done +if test ${ac_cv_search_deflateSetHeader+y} +then : + +else case e in #( + e) ac_cv_search_deflateSetHeader=no ;; +esac +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_deflateSetHeader" >&5 +printf '%s\n' "$ac_cv_search_deflateSetHeader" >&6; } +ac_res=$ac_cv_search_deflateSetHeader +if test "$ac_res" != no +then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +else case e in #( + e) + zlib_ok=no + ;; +esac +fi + +fi +if test $zlib_ok = no +then : + + ZLIB_OBJS=\${ZLIB_OBJS} + + ZLIB_SRCS=\${ZLIB_SRCS} + + ZLIB_INCLUDE=-I\${ZLIB_DIR} + + +printf '%s\n' "#define TCL_WITH_INTERNAL_ZLIB 1" >>confdefs.h + + +fi + +#------------------------------------------------------------------------ +# Add stuff for libtommath + +libtommath_ok=yes + +# Check whether --with-system-libtommath was given. +if test ${with_system_libtommath+y} +then : + withval=$with_system_libtommath; libtommath_ok=${withval} +fi + +if test x"${libtommath_ok}" = x -o x"${libtommath_ok}" != xno; then + ac_fn_c_check_header_compile "$LINENO" "tommath.h" "ac_cv_header_tommath_h" "$ac_includes_default" +if test "x$ac_cv_header_tommath_h" = xyes +then : + + ac_fn_c_check_type "$LINENO" "mp_int" "ac_cv_type_mp_int" "#include +" +if test "x$ac_cv_type_mp_int" = xyes +then : + +else case e in #( + e) libtommath_ok=no ;; +esac +fi + +else case e in #( + e) + libtommath_ok=no ;; +esac +fi + + if test $libtommath_ok = yes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for mp_log_u32 in -ltommath" >&5 +printf %s "checking for mp_log_u32 in -ltommath... " >&6; } +if test ${ac_cv_lib_tommath_mp_log_u32+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-ltommath $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char mp_log_u32 (void); +int +main (void) +{ +return mp_log_u32 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_tommath_mp_log_u32=yes +else case e in #( + e) ac_cv_lib_tommath_mp_log_u32=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_tommath_mp_log_u32" >&5 +printf '%s\n' "$ac_cv_lib_tommath_mp_log_u32" >&6; } +if test "x$ac_cv_lib_tommath_mp_log_u32" = xyes +then : + MATH_LIBS="$MATH_LIBS -ltommath" +else case e in #( + e) + libtommath_ok=no ;; +esac +fi + +fi +fi +if test $libtommath_ok = yes +then : + + TCL_PC_REQUIRES_PRIVATE='libtommath >= 1.2.0,' + + TCL_PC_CFLAGS='-DTCL_WITH_EXTERNAL_TOMMATH' + + +printf '%s\n' "#define TCL_WITH_EXTERNAL_TOMMATH 1" >>confdefs.h + + +else case e in #( + e) + TOMMATH_OBJS=\${TOMMATH_OBJS} + + TOMMATH_SRCS=\${TOMMATH_SRCS} + + TOMMATH_INCLUDE=-I\${TOMMATH_DIR} + + ;; +esac +fi + +#-------------------------------------------------------------------- +# 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. +#-------------------------------------------------------------------- + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_RANLIB+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +printf '%s\n' "$RANLIB" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_RANLIB+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +printf '%s\n' "$ac_ct_RANLIB" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + + + + # Step 0.a: Enable 64 bit support? + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if 64bit support is requested" >&5 +printf %s "checking if 64bit support is requested... " >&6; } + # Check whether --enable-64bit was given. +if test ${enable_64bit+y} +then : + enableval=$enable_64bit; do64bit=$enableval +else case e in #( + e) do64bit=no ;; +esac +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $do64bit" >&5 +printf '%s\n' "$do64bit" >&6; } + + # Step 0.b: Enable Solaris 64 bit VIS support? + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if 64bit Sparc VIS support is requested" >&5 +printf %s "checking if 64bit Sparc VIS support is requested... " >&6; } + # Check whether --enable-64bit-vis was given. +if test ${enable_64bit_vis+y} +then : + enableval=$enable_64bit_vis; do64bitVIS=$enableval +else case e in #( + e) do64bitVIS=no ;; +esac +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $do64bitVIS" >&5 +printf '%s\n' "$do64bitVIS" >&6; } + # Force 64bit on with VIS + if test "$do64bitVIS" = "yes" +then : + do64bit=yes +fi + + # Step 0.c: Check if visibility support is available. Do this here so + # that platform specific alternatives can be used below if this fails. + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if compiler supports visibility \"hidden\"" >&5 +printf %s "checking if compiler supports visibility \"hidden\"... " >&6; } +if test ${tcl_cv_cc_visibility_hidden+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + extern __attribute__((__visibility__("hidden"))) void f(void); + void f(void) {} +int +main (void) +{ +f(); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_cc_visibility_hidden=yes +else case e in #( + e) tcl_cv_cc_visibility_hidden=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_visibility_hidden" >&5 +printf '%s\n' "$tcl_cv_cc_visibility_hidden" >&6; } + if test $tcl_cv_cc_visibility_hidden = yes +then : + + +printf '%s\n' "#define MODULE_SCOPE extern __attribute__((__visibility__(\"hidden\")))" >>confdefs.h + + +printf '%s\n' "#define HAVE_HIDDEN 1" >>confdefs.h + + +fi + + # Step 0.d: Disable -rpath support? + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if rpath support is requested" >&5 +printf %s "checking if rpath support is requested... " >&6; } + # Check whether --enable-rpath was given. +if test ${enable_rpath+y} +then : + enableval=$enable_rpath; doRpath=$enableval +else case e in #( + e) doRpath=yes ;; +esac +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $doRpath" >&5 +printf '%s\n' "$doRpath" >&6; } + + # Step 1: set the variable "system" to hold the name and version number + # for the system. + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking system version" >&5 +printf %s "checking system version... " >&6; } +if test ${tcl_cv_sys_version+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + if test "${TEA_PLATFORM}" = "windows" ; then + tcl_cv_sys_version=windows + else + tcl_cv_sys_version=`uname -s`-`uname -r` + if test "$?" -ne 0 ; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: can't find uname command" >&5 +printf '%s\n' "$as_me: WARNING: can't find uname command" >&2;} + tcl_cv_sys_version=unknown + else + if test "`uname -s`" = "AIX" ; then + tcl_cv_sys_version=AIX-`uname -v`.`uname -r` + fi + if test "`uname -s`" = "NetBSD" -a -f /etc/debian_version ; then + tcl_cv_sys_version=NetBSD-Debian + fi + fi + fi + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_sys_version" >&5 +printf '%s\n' "$tcl_cv_sys_version" >&6; } + system=$tcl_cv_sys_version + + + # Step 2: check for existence of -ldl library. This is needed because + # Linux can use either -ldl or -ldld for dynamic loading. + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +printf %s "checking for dlopen in -ldl... " >&6; } +if test ${ac_cv_lib_dl_dlopen+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (void); +int +main (void) +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_dl_dlopen=yes +else case e in #( + e) ac_cv_lib_dl_dlopen=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +printf '%s\n' "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes +then : + have_dl=yes +else case e in #( + e) have_dl=no ;; +esac +fi + + + # Require ranlib early so we can override it in special cases below. + + + + # Step 3: set configuration options based on system name and version. + + do64bit_ok=no + # default to '{$LIBS}' and set to "" on per-platform necessary basis + SHLIB_LD_LIBS='${LIBS}' + LDFLAGS_ORIG="$LDFLAGS" + # When ld needs options to work in 64-bit mode, put them in + # LDFLAGS_ARCH so they eventually end up in LDFLAGS even if [load] + # is disabled by the user. [Bug 1016796] + LDFLAGS_ARCH="" + UNSHARED_LIB_SUFFIX="" + TCL_TRIM_DOTS='`echo ${VERSION} | tr -d .`' + ECHO_VERSION='`echo ${VERSION}`' + TCL_LIB_VERSIONS_OK=ok + CFLAGS_DEBUG=-g + if test "$GCC" = yes +then : + + CFLAGS_OPTIMIZE=-O2 + CFLAGS_WARNING="-Wall -Wextra -Wshadow -Wundef -Wwrite-strings -Wpointer-arith" + case "${CC}" in + *++|*++-*) + ;; + *) + CFLAGS_WARNING="${CFLAGS_WARNING} -Wc++-compat -fextended-identifiers" + ;; + esac + + +else case e in #( + e) + CFLAGS_OPTIMIZE=-O + CFLAGS_WARNING="" + ;; +esac +fi + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ar", so it can be a program name with args. +set dummy ${ac_tool_prefix}ar; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_AR+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$AR"; then + ac_cv_prog_AR="$AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_AR="${ac_tool_prefix}ar" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +AR=$ac_cv_prog_AR +if test -n "$AR"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 +printf '%s\n' "$AR" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_AR"; then + ac_ct_AR=$AR + # Extract the first word of "ar", so it can be a program name with args. +set dummy ar; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_AR+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_AR"; then + ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_AR="ar" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_AR=$ac_cv_prog_ac_ct_AR +if test -n "$ac_ct_AR"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 +printf '%s\n' "$ac_ct_AR" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + if test "x$ac_ct_AR" = x; then + AR="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + AR=$ac_ct_AR + fi +else + AR="$ac_cv_prog_AR" +fi + + STLIB_LD='${AR} cr' + LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH" + PLAT_OBJS="" + PLAT_SRCS="" + LDAIX_SRC="" + if test "x${SHLIB_VERSION}" = x +then : + SHLIB_VERSION="1.0" +fi + case $system in + AIX-*) + if test "$GCC" != "yes" +then : + + # AIX requires the _r compiler when gcc isn't being used + case "${CC}" in + *_r|*_r\ *) + # ok ... + ;; + *) + # Make sure only first arg gets _r + CC=`echo "$CC" | sed -e 's/^\([^ ]*\)/\1_r/'` + ;; + esac + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: Using $CC for compiling with threads" >&5 +printf '%s\n' "Using $CC for compiling with threads" >&6; } + +fi + LIBS="$LIBS -lc" + SHLIB_CFLAGS="" + SHLIB_SUFFIX=".so" + + DL_OBJS="tclLoadDl.o" + LD_LIBRARY_PATH_VAR="LIBPATH" + + # ldAix No longer needed with use of -bexpall/-brtl + # but some extensions may still reference it + LDAIX_SRC='$(UNIX_DIR)/ldAix' + + # Check to enable 64-bit flags for compiler/linker + if test "$do64bit" = yes +then : + + if test "$GCC" = yes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC on $system" >&5 +printf '%s\n' "$as_me: WARNING: 64bit mode not supported with GCC on $system" >&2;} + +else case e in #( + e) + do64bit_ok=yes + CFLAGS="$CFLAGS -q64" + LDFLAGS_ARCH="-q64" + RANLIB="${RANLIB} -X64" + AR="${AR} -X64" + SHLIB_LD_FLAGS="-b64" + ;; +esac +fi + +fi + + if test "`uname -m`" = ia64 +then : + + # AIX-5 uses ELF style dynamic libraries on IA-64, but not PPC + SHLIB_LD="/usr/ccs/bin/ld -G -z text" + # AIX-5 has dl* in libc.so + DL_LIBS="" + if test "$GCC" = yes +then : + + CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' + +else case e in #( + e) + CC_SEARCH_FLAGS='-R${LIB_RUNTIME_DIR}' + ;; +esac +fi + LD_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' + +else case e in #( + e) + if test "$GCC" = yes +then : + + SHLIB_LD='${CC} -shared -Wl,-bexpall' + +else case e in #( + e) + SHLIB_LD="/bin/ld -bhalt:4 -bM:SRE -bexpall -H512 -T512 -bnoentry" + LDFLAGS="$LDFLAGS -brtl" + ;; +esac +fi + SHLIB_LD="${SHLIB_LD} ${SHLIB_LD_FLAGS}" + DL_LIBS="-ldl" + CC_SEARCH_FLAGS='-L${LIB_RUNTIME_DIR}' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + ;; +esac +fi + ;; + BeOS*) + SHLIB_CFLAGS="-fPIC" + SHLIB_LD='${CC} -nostart' + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="-ldl" + + #----------------------------------------------------------- + # Check for inet_ntoa in -lbind, for BeOS (which also needs + # -lsocket, even if the network functions are in -lnet which + # is always linked to, for compatibility. + #----------------------------------------------------------- + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for inet_ntoa in -lbind" >&5 +printf %s "checking for inet_ntoa in -lbind... " >&6; } +if test ${ac_cv_lib_bind_inet_ntoa+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lbind $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char inet_ntoa (void); +int +main (void) +{ +return inet_ntoa (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_bind_inet_ntoa=yes +else case e in #( + e) ac_cv_lib_bind_inet_ntoa=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bind_inet_ntoa" >&5 +printf '%s\n' "$ac_cv_lib_bind_inet_ntoa" >&6; } +if test "x$ac_cv_lib_bind_inet_ntoa" = xyes +then : + LIBS="$LIBS -lbind -lsocket" +fi + + ;; + BSD/OS-2.1*|BSD/OS-3*) + SHLIB_CFLAGS="" + SHLIB_LD="shlicc -r" + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="-ldl" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + BSD/OS-4.*) + SHLIB_CFLAGS="-export-dynamic -fPIC" + SHLIB_LD='${CC} -shared' + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="-ldl" + LDFLAGS="$LDFLAGS -export-dynamic" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + CYGWIN_*|MINGW32_*|MSYS_*) + SHLIB_CFLAGS="-fno-common" + SHLIB_LD='${CC} -shared -Wl,--out-implib,$(patsubst cyg%.dll,lib%.dll,$@).a' + SHLIB_SUFFIX=".dll" + DL_OBJS="tclLoadDl.o" + PLAT_OBJS='${CYGWIN_OBJS}' + PLAT_SRCS='${CYGWIN_SRCS}' + DL_LIBS="-ldl" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for Cygwin version of gcc" >&5 +printf %s "checking for Cygwin version of gcc... " >&6; } +if test ${ac_cv_cygwin+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #ifdef __CYGWIN__ + #error cygwin + #endif + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_cygwin=no +else case e in #( + e) ac_cv_cygwin=yes ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cygwin" >&5 +printf '%s\n' "$ac_cv_cygwin" >&6; } + if test "$ac_cv_cygwin" = "no"; then + as_fn_error $? "${CC} is not a cygwin compiler." "$LINENO" 5 + fi + do64bit_ok=yes + if test "x${SHARED_BUILD}" = "x1"; then + echo "running cd ../win; ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args --enable-64bit --host=x86_64-w64-mingw32" + # The eval makes quoting arguments work. + if cd ../win; eval ${CONFIG_SHELL-/bin/sh} ./configure $ac_configure_args --enable-64bit --host=x86_64-w64-mingw32; cd ../unix + then : + else + { echo "configure: error: configure failed for ../win" 1>&2; exit 1; } + fi + fi + ;; + dgux*) + SHLIB_CFLAGS="-K PIC" + SHLIB_LD='${CC} -G' + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="-ldl" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + Haiku*) + LDFLAGS="$LDFLAGS -Wl,--export-dynamic" + SHLIB_CFLAGS="-fPIC" + SHLIB_SUFFIX=".so" + SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS} -shared' + DL_OBJS="tclLoadDl.o" + DL_LIBS="-lroot" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for inet_ntoa in -lnetwork" >&5 +printf %s "checking for inet_ntoa in -lnetwork... " >&6; } +if test ${ac_cv_lib_network_inet_ntoa+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lnetwork $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char inet_ntoa (void); +int +main (void) +{ +return inet_ntoa (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_network_inet_ntoa=yes +else case e in #( + e) ac_cv_lib_network_inet_ntoa=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_network_inet_ntoa" >&5 +printf '%s\n' "$ac_cv_lib_network_inet_ntoa" >&6; } +if test "x$ac_cv_lib_network_inet_ntoa" = xyes +then : + LIBS="$LIBS -lnetwork" +fi + + ;; + HP-UX-*.11.*) + # Use updated header definitions where possible + +printf '%s\n' "#define _XOPEN_SOURCE_EXTENDED 1" >>confdefs.h + + +printf '%s\n' "#define _XOPEN_SOURCE 1" >>confdefs.h + + LIBS="$LIBS -lxnet" # Use the XOPEN network library + + if test "`uname -m`" = ia64 +then : + + SHLIB_SUFFIX=".so" + +else case e in #( + e) + SHLIB_SUFFIX=".sl" + ;; +esac +fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +printf %s "checking for shl_load in -ldld... " >&6; } +if test ${ac_cv_lib_dld_shl_load+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (void); +int +main (void) +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_dld_shl_load=yes +else case e in #( + e) ac_cv_lib_dld_shl_load=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +printf '%s\n' "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes +then : + tcl_ok=yes +else case e in #( + e) tcl_ok=no ;; +esac +fi + + if test "$tcl_ok" = yes +then : + + SHLIB_CFLAGS="+z" + SHLIB_LD="ld -b" + DL_OBJS="tclLoadShl.o" + DL_LIBS="-ldld" + LDFLAGS="$LDFLAGS -Wl,-E" + CC_SEARCH_FLAGS='-Wl,+s,+b,${LIB_RUNTIME_DIR}:.' + LD_SEARCH_FLAGS='+s +b ${LIB_RUNTIME_DIR}:.' + LD_LIBRARY_PATH_VAR="SHLIB_PATH" + +fi + if test "$GCC" = yes +then : + + SHLIB_LD='${CC} -shared' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + +else case e in #( + e) + CFLAGS="$CFLAGS -z" + ;; +esac +fi + + # Users may want PA-RISC 1.1/2.0 portable code - needs HP cc + #CFLAGS="$CFLAGS +DAportable" + + # Check to enable 64-bit flags for compiler/linker + if test "$do64bit" = "yes" +then : + + if test "$GCC" = yes +then : + + case `${CC} -dumpmachine` in + hppa64*) + # 64-bit gcc in use. Fix flags for GNU ld. + do64bit_ok=yes + SHLIB_LD='${CC} -shared' + if test $doRpath = yes +then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + ;; + *) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC on $system" >&5 +printf '%s\n' "$as_me: WARNING: 64bit mode not supported with GCC on $system" >&2;} + ;; + esac + +else case e in #( + e) + do64bit_ok=yes + CFLAGS="$CFLAGS +DD64" + LDFLAGS_ARCH="+DD64" + ;; +esac +fi + +fi ;; + HP-UX-*.08.*|HP-UX-*.09.*|HP-UX-*.10.*) + SHLIB_SUFFIX=".sl" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +printf %s "checking for shl_load in -ldld... " >&6; } +if test ${ac_cv_lib_dld_shl_load+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (void); +int +main (void) +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_dld_shl_load=yes +else case e in #( + e) ac_cv_lib_dld_shl_load=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +printf '%s\n' "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes +then : + tcl_ok=yes +else case e in #( + e) tcl_ok=no ;; +esac +fi + + if test "$tcl_ok" = yes +then : + + SHLIB_CFLAGS="+z" + SHLIB_LD="ld -b" + SHLIB_LD_LIBS="" + DL_OBJS="tclLoadShl.o" + DL_LIBS="-ldld" + LDFLAGS="$LDFLAGS -Wl,-E" + CC_SEARCH_FLAGS='-Wl,+s,+b,${LIB_RUNTIME_DIR}:.' + LD_SEARCH_FLAGS='+s +b ${LIB_RUNTIME_DIR}:.' + LD_LIBRARY_PATH_VAR="SHLIB_PATH" + +fi ;; + IRIX-5.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -shared -rdata_shared" + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="" + case " $LIBOBJS " in + *" mkstemp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS mkstemp.$ac_objext" + ;; +esac + + if test $doRpath = yes +then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}' +fi + ;; + IRIX-6.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -n32 -shared -rdata_shared" + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="" + case " $LIBOBJS " in + *" mkstemp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS mkstemp.$ac_objext" + ;; +esac + + if test $doRpath = yes +then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}' +fi + if test "$GCC" = yes +then : + + CFLAGS="$CFLAGS -mabi=n32" + LDFLAGS="$LDFLAGS -mabi=n32" + +else case e in #( + e) + case $system in + IRIX-6.3) + # Use to build 6.2 compatible binaries on 6.3. + CFLAGS="$CFLAGS -n32 -D_OLD_TERMIOS" + ;; + *) + CFLAGS="$CFLAGS -n32" + ;; + esac + LDFLAGS="$LDFLAGS -n32" + ;; +esac +fi + ;; + IRIX64-6.*) + SHLIB_CFLAGS="" + SHLIB_LD="ld -n32 -shared -rdata_shared" + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="" + case " $LIBOBJS " in + *" mkstemp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS mkstemp.$ac_objext" + ;; +esac + + if test $doRpath = yes +then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}' +fi + + # Check to enable 64-bit flags for compiler/linker + + if test "$do64bit" = yes +then : + + if test "$GCC" = yes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported by gcc" >&5 +printf '%s\n' "$as_me: WARNING: 64bit mode not supported by gcc" >&2;} + +else case e in #( + e) + do64bit_ok=yes + SHLIB_LD="ld -64 -shared -rdata_shared" + CFLAGS="$CFLAGS -64" + LDFLAGS_ARCH="-64" + ;; +esac +fi + +fi + ;; + Linux*|GNU*|NetBSD-Debian|DragonFly-*|FreeBSD-*) + SHLIB_CFLAGS="-fPIC -fno-common" + SHLIB_SUFFIX=".so" + + CFLAGS_OPTIMIZE="-O2" + # egcs-2.91.66 on Redhat Linux 6.0 generates lots of warnings + # when you inline the string and math operations. Turn this off to + # get rid of the warnings. + #CFLAGS_OPTIMIZE="${CFLAGS_OPTIMIZE} -D__NO_STRING_INLINES -D__NO_MATH_INLINES" + + SHLIB_LD='${CC} ${CFLAGS} ${LDFLAGS} -shared' + DL_OBJS="tclLoadDl.o" + DL_LIBS="-ldl" + LDFLAGS="$LDFLAGS -Wl,--export-dynamic" + + case $system in + DragonFly-*|FreeBSD-*) + # The -pthread needs to go in the LDFLAGS, not LIBS + LIBS=`echo $LIBS | sed s/-pthread//` + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LDFLAGS="$LDFLAGS $PTHREAD_LIBS" + ;; + esac + + if test $doRpath = yes +then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + if test "`uname -m`" = "alpha" +then : + CFLAGS="$CFLAGS -mieee" +fi + if test $do64bit = yes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -m64 flag" >&5 +printf %s "checking if compiler accepts -m64 flag... " >&6; } +if test ${tcl_cv_cc_m64+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -m64" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_cc_m64=yes +else case e in #( + e) tcl_cv_cc_m64=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_m64" >&5 +printf '%s\n' "$tcl_cv_cc_m64" >&6; } + if test $tcl_cv_cc_m64 = yes +then : + + CFLAGS="$CFLAGS -m64" + do64bit_ok=yes + +fi + +fi + + # The combo of gcc + glibc has a bug related to inlining of + # functions like strtol()/strtoul(). The -fno-builtin flag should address + # this problem but it does not work. The -fno-inline flag is kind + # of overkill but it works. Disable inlining only when one of the + # files in compat/*.c is being linked in. + + if test x"${USE_COMPAT}" != x +then : + CFLAGS="$CFLAGS -fno-inline" +fi + ;; + Lynx*) + SHLIB_CFLAGS="-fPIC" + SHLIB_SUFFIX=".so" + CFLAGS_OPTIMIZE=-02 + SHLIB_LD='${CC} -shared' + DL_OBJS="tclLoadDl.o" + DL_LIBS="-mshared -ldl" + LD_FLAGS="-Wl,--export-dynamic" + if test $doRpath = yes +then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + ;; + OpenBSD-*) + arch=`arch -s` + case "$arch" in + alpha|sparc64) + SHLIB_CFLAGS="-fPIC" + ;; + *) + SHLIB_CFLAGS="-fpic" + ;; + esac + SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="" + if test $doRpath = yes +then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + SHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.so.${SHLIB_VERSION}' + LDFLAGS="-Wl,-export-dynamic" + CFLAGS_OPTIMIZE="-O2" + # On OpenBSD: Compile with -pthread + # Don't link with -lpthread + LIBS=`echo $LIBS | sed s/-lpthread//` + CFLAGS="$CFLAGS -pthread" + # OpenBSD doesn't do version numbers with dots. + UNSHARED_LIB_SUFFIX='${TCL_TRIM_DOTS}.a' + TCL_LIB_VERSIONS_OK=nodots + ;; + NetBSD-*) + # NetBSD has ELF and can use 'cc -shared' to build shared libs + SHLIB_CFLAGS="-fPIC" + SHLIB_LD='${CC} ${SHLIB_CFLAGS} -shared' + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="" + LDFLAGS="$LDFLAGS -export-dynamic" + if test $doRpath = yes +then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' +fi + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + # The -pthread needs to go in the CFLAGS, not LIBS + LIBS=`echo $LIBS | sed s/-pthread//` + CFLAGS="$CFLAGS -pthread" + LDFLAGS="$LDFLAGS -pthread" + ;; + Darwin-*) + CFLAGS_OPTIMIZE="-O2" + SHLIB_CFLAGS="-fno-common" + # To avoid discrepancies between what headers configure sees during + # preprocessing tests and compiling tests, move any -isysroot and + # -mmacosx-version-min flags from CFLAGS to CPPFLAGS: + CPPFLAGS="${CPPFLAGS} `echo " ${CFLAGS}" | \ + awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ + if ($i~/^(isysroot|mmacosx-version-min)/) print "-"$i}'`" + CFLAGS="`echo " ${CFLAGS}" | \ + awk 'BEGIN {FS=" +-";ORS=" "}; {for (i=2;i<=NF;i++) \ + if (!($i~/^(isysroot|mmacosx-version-min)/)) print "-"$i}'`" + if test $do64bit = yes +then : + + case `arch` in + ppc) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -arch ppc64 flag" >&5 +printf %s "checking if compiler accepts -arch ppc64 flag... " >&6; } +if test ${tcl_cv_cc_arch_ppc64+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_cc_arch_ppc64=yes +else case e in #( + e) tcl_cv_cc_arch_ppc64=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_arch_ppc64" >&5 +printf '%s\n' "$tcl_cv_cc_arch_ppc64" >&6; } + if test $tcl_cv_cc_arch_ppc64 = yes +then : + + CFLAGS="$CFLAGS -arch ppc64 -mpowerpc64 -mcpu=G5" + do64bit_ok=yes + +fi;; + i386|x86_64) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -arch x86_64 flag" >&5 +printf %s "checking if compiler accepts -arch x86_64 flag... " >&6; } +if test ${tcl_cv_cc_arch_x86_64+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -arch x86_64" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_cc_arch_x86_64=yes +else case e in #( + e) tcl_cv_cc_arch_x86_64=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_arch_x86_64" >&5 +printf '%s\n' "$tcl_cv_cc_arch_x86_64" >&6; } + if test $tcl_cv_cc_arch_x86_64 = yes +then : + + CFLAGS="$CFLAGS -arch x86_64" + do64bit_ok=yes + +fi;; + arm64) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if compiler accepts -arch arm64 flag" >&5 +printf %s "checking if compiler accepts -arch arm64 flag... " >&6; } +if test ${tcl_cv_cc_arch_arm64+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS + CFLAGS="$CFLAGS -arch arm64" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_cc_arch_arm64=yes +else case e in #( + e) tcl_cv_cc_arch_arm64=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_arch_arm64" >&5 +printf '%s\n' "$tcl_cv_cc_arch_arm64" >&6; } + if test $tcl_cv_cc_arch_arm64 = yes +then : + + CFLAGS="$CFLAGS -arch arm64" + do64bit_ok=yes + +fi;; + *) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: Don't know how enable 64-bit on architecture \`arch\`" >&5 +printf '%s\n' "$as_me: WARNING: Don't know how enable 64-bit on architecture \`arch\`" >&2;};; + esac + +else case e in #( + e) + # Check for combined 32-bit and 64-bit fat build + if echo "$CFLAGS " |grep -E -q -- '-arch (ppc64|x86_64|arm64) ' \ + && echo "$CFLAGS " |grep -E -q -- '-arch (ppc|i386) ' +then : + + fat_32_64=yes +fi + ;; +esac +fi + SHLIB_LD='${CC} -dynamiclib ${CFLAGS} ${LDFLAGS}' + SHLIB_SUFFIX=".dylib" + DL_OBJS="tclLoadDyld.o" + DL_LIBS="" + LDFLAGS="$LDFLAGS -headerpad_max_install_names" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if ld accepts -search_paths_first flag" >&5 +printf %s "checking if ld accepts -search_paths_first flag... " >&6; } +if test ${tcl_cv_ld_search_paths_first+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_ldflags=$LDFLAGS + LDFLAGS="$LDFLAGS -Wl,-search_paths_first" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ +int i; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_ld_search_paths_first=yes +else case e in #( + e) tcl_cv_ld_search_paths_first=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$hold_ldflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_ld_search_paths_first" >&5 +printf '%s\n' "$tcl_cv_ld_search_paths_first" >&6; } + if test $tcl_cv_ld_search_paths_first = yes +then : + + LDFLAGS="$LDFLAGS -Wl,-search_paths_first" + +fi + if test "$tcl_cv_cc_visibility_hidden" != yes +then : + + +printf '%s\n' "#define MODULE_SCOPE __private_extern__" >>confdefs.h + + tcl_cv_cc_visibility_hidden=yes + +fi + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + LD_LIBRARY_PATH_VAR="DYLD_FALLBACK_LIBRARY_PATH" + +printf '%s\n' "#define MAC_OSX_TCL 1" >>confdefs.h + + PLAT_OBJS='${MAC_OSX_OBJS}' + PLAT_SRCS='${MAC_OSX_SRCS}' + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to use CoreFoundation" >&5 +printf %s "checking whether to use CoreFoundation... " >&6; } + # Check whether --enable-corefoundation was given. +if test ${enable_corefoundation+y} +then : + enableval=$enable_corefoundation; tcl_corefoundation=$enableval +else case e in #( + e) tcl_corefoundation=yes ;; +esac +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_corefoundation" >&5 +printf '%s\n' "$tcl_corefoundation" >&6; } + if test $tcl_corefoundation = yes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for CoreFoundation.framework" >&5 +printf %s "checking for CoreFoundation.framework... " >&6; } +if test ${tcl_cv_lib_corefoundation+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_libs=$LIBS + if test "$fat_32_64" = yes +then : + + for v in CFLAGS CPPFLAGS LDFLAGS; do + # On Tiger there is no 64-bit CF, so remove 64-bit + # archs from CFLAGS et al. while testing for + # presence of CF. 64-bit CF is disabled in + # tclUnixPort.h if necessary. + eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc64 / /g" -e "s/-arch x86_64 / /g"`"' + done +fi + LIBS="$LIBS -framework CoreFoundation" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +CFBundleRef b = CFBundleGetMainBundle(); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_lib_corefoundation=yes +else case e in #( + e) tcl_cv_lib_corefoundation=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + if test "$fat_32_64" = yes +then : + + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval $v'="$hold_'$v'"' + done +fi + LIBS=$hold_libs ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_lib_corefoundation" >&5 +printf '%s\n' "$tcl_cv_lib_corefoundation" >&6; } + if test $tcl_cv_lib_corefoundation = yes +then : + + LIBS="$LIBS -framework CoreFoundation" + +printf '%s\n' "#define HAVE_COREFOUNDATION 1" >>confdefs.h + + +else case e in #( + e) tcl_corefoundation=no ;; +esac +fi + if test "$fat_32_64" = yes -a $tcl_corefoundation = yes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for 64-bit CoreFoundation" >&5 +printf %s "checking for 64-bit CoreFoundation... " >&6; } +if test ${tcl_cv_lib_corefoundation_64+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval 'hold_'$v'="$'$v'";'$v'="`echo "$'$v' "|sed -e "s/-arch ppc / /g" -e "s/-arch i386 / /g"`"' + done + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +CFBundleRef b = CFBundleGetMainBundle(); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_lib_corefoundation_64=yes +else case e in #( + e) tcl_cv_lib_corefoundation_64=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + for v in CFLAGS CPPFLAGS LDFLAGS; do + eval $v'="$hold_'$v'"' + done ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_lib_corefoundation_64" >&5 +printf '%s\n' "$tcl_cv_lib_corefoundation_64" >&6; } + if test $tcl_cv_lib_corefoundation_64 = no +then : + + +printf '%s\n' "#define NO_COREFOUNDATION_64 1" >>confdefs.h + + LDFLAGS="$LDFLAGS -Wl,-no_arch_warnings" + +fi + +fi + +fi + ;; + OS/390-*) + SHLIB_LD_LIBS="" + CFLAGS_OPTIMIZE="" # Optimizer is buggy + +printf '%s\n' "#define _OE_SOCKETS 1" >>confdefs.h + + ;; + OSF1-V*) + # Digital OSF/1 + SHLIB_CFLAGS="" + if test "$SHARED_BUILD" = 1 +then : + + SHLIB_LD='${CC} -shared' + +else case e in #( + e) + SHLIB_LD='${CC} -non_shared' + ;; +esac +fi + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="" + if test $doRpath = yes +then : + + CC_SEARCH_FLAGS='"-Wl,-rpath,${LIB_RUNTIME_DIR}"' + LD_SEARCH_FLAGS='-rpath ${LIB_RUNTIME_DIR}' +fi + if test "$GCC" = yes +then : + CFLAGS="$CFLAGS -mieee" +else case e in #( + e) + CFLAGS="$CFLAGS -DHAVE_TZSET -std1 -ieee" ;; +esac +fi + # see pthread_intro(3) for pthread support on osf1, k.furukawa + CFLAGS="$CFLAGS -DHAVE_PTHREAD_ATTR_SETSTACKSIZE" + CFLAGS="$CFLAGS -DTCL_THREAD_STACK_MIN=PTHREAD_STACK_MIN*64" + LIBS=`echo $LIBS | sed s/-lpthreads//` + if test "$GCC" = yes +then : + + LIBS="$LIBS -lpthread -lmach -lexc" + +else case e in #( + e) + CFLAGS="$CFLAGS -pthread" + LDFLAGS="$LDFLAGS -pthread" + ;; +esac +fi + ;; + QNX-6*) + # QNX RTP + # This may work for all QNX, but it was only reported for v6. + SHLIB_LD="ld -Bshareable -x" + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + # dlopen is in -lc on QNX + DL_LIBS="" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + SCO_SV-3.2*) + # Note, dlopen is available only on SCO 3.2.5 and greater. However, + # this test works, since "uname -s" was non-standard in 3.2.4 and + # below. + if test "$GCC" = yes +then : + + SHLIB_CFLAGS="-fPIC -melf" + LDFLAGS="$LDFLAGS -melf -Wl,-Bexport" + +else case e in #( + e) + SHLIB_CFLAGS="-Kpic -belf" + LDFLAGS="$LDFLAGS -belf -Wl,-Bexport" + ;; +esac +fi + SHLIB_LD="ld -G" + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + SunOS-5.[0-6]) + # Careful to not let 5.10+ fall into this case + + # Note: If _REENTRANT isn't defined, then Solaris + # won't define thread-safe library routines. + + +printf '%s\n' "#define _REENTRANT 1" >>confdefs.h + + +printf '%s\n' "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h + + + SHLIB_CFLAGS="-KPIC" + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="-ldl" + if test "$GCC" = yes +then : + + SHLIB_LD='${CC} -shared' + CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + +else case e in #( + e) + SHLIB_LD="/usr/ccs/bin/ld -G -z text" + CC_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + ;; +esac +fi + ;; + SunOS-5*) + # Note: If _REENTRANT isn't defined, then Solaris + # won't define thread-safe library routines. + + +printf '%s\n' "#define _REENTRANT 1" >>confdefs.h + + +printf '%s\n' "#define _POSIX_PTHREAD_SEMANTICS 1" >>confdefs.h + + + SHLIB_CFLAGS="-KPIC" + + # Check to enable 64-bit flags for compiler/linker + if test "$do64bit" = yes +then : + + arch=`isainfo` + if test "$arch" = "sparcv9 sparc" +then : + + if test "$GCC" = yes +then : + + if test "`${CC} -dumpversion | awk -F. '{print $1}'`" -lt 3 +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC < 3.2 on $system" >&5 +printf '%s\n' "$as_me: WARNING: 64bit mode not supported with GCC < 3.2 on $system" >&2;} + +else case e in #( + e) + do64bit_ok=yes + CFLAGS="$CFLAGS -m64 -mcpu=v9" + LDFLAGS="$LDFLAGS -m64 -mcpu=v9" + SHLIB_CFLAGS="-fPIC" + ;; +esac +fi + +else case e in #( + e) + do64bit_ok=yes + if test "$do64bitVIS" = yes +then : + + CFLAGS="$CFLAGS -xarch=v9a" + LDFLAGS_ARCH="-xarch=v9a" + +else case e in #( + e) + CFLAGS="$CFLAGS -xarch=v9" + LDFLAGS_ARCH="-xarch=v9" + ;; +esac +fi + # Solaris 64 uses this as well + #LD_LIBRARY_PATH_VAR="LD_LIBRARY_PATH_64" + ;; +esac +fi + +else case e in #( + e) if test "$arch" = "amd64 i386" +then : + + if test "$GCC" = yes +then : + + case $system in + SunOS-5.1[1-9]*|SunOS-5.[2-9][0-9]*) + do64bit_ok=yes + CFLAGS="$CFLAGS -m64" + LDFLAGS="$LDFLAGS -m64";; + *) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported with GCC on $system" >&5 +printf '%s\n' "$as_me: WARNING: 64bit mode not supported with GCC on $system" >&2;};; + esac + +else case e in #( + e) + do64bit_ok=yes + case $system in + SunOS-5.1[1-9]*|SunOS-5.[2-9][0-9]*) + CFLAGS="$CFLAGS -m64" + LDFLAGS="$LDFLAGS -m64";; + *) + CFLAGS="$CFLAGS -xarch=amd64" + LDFLAGS="$LDFLAGS -xarch=amd64";; + esac + ;; +esac +fi + +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: 64bit mode not supported for $arch" >&5 +printf '%s\n' "$as_me: WARNING: 64bit mode not supported for $arch" >&2;} ;; +esac +fi ;; +esac +fi + +fi + + #-------------------------------------------------------------------- + # On Solaris 5.x i386 with the sunpro compiler we need to link + # with sunmath to get floating point rounding control + #-------------------------------------------------------------------- + if test "$GCC" = yes +then : + use_sunmath=no +else case e in #( + e) + arch=`isainfo` + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to use -lsunmath for fp rounding control" >&5 +printf %s "checking whether to use -lsunmath for fp rounding control... " >&6; } + if test "$arch" = "amd64 i386" -o "$arch" = "i386" +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } + MATH_LIBS="-lsunmath $MATH_LIBS" + ac_fn_c_check_header_compile "$LINENO" "sunmath.h" "ac_cv_header_sunmath_h" "$ac_includes_default" +if test "x$ac_cv_header_sunmath_h" = xyes +then : + +fi + + use_sunmath=yes + +else case e in #( + e) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } + use_sunmath=no + ;; +esac +fi + ;; +esac +fi + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="-ldl" + if test "$GCC" = yes +then : + + SHLIB_LD='${CC} -shared' + CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' + LD_SEARCH_FLAGS=${CC_SEARCH_FLAGS} + if test "$do64bit_ok" = yes +then : + + if test "$arch" = "sparcv9 sparc" +then : + + # We need to specify -static-libgcc or we need to + # add the path to the sparv9 libgcc. + SHLIB_LD="$SHLIB_LD -m64 -mcpu=v9 -static-libgcc" + # for finding sparcv9 libgcc, get the regular libgcc + # path, remove so name and append 'sparcv9' + #v9gcclibdir="`gcc -print-file-name=libgcc_s.so` | ..." + #CC_SEARCH_FLAGS="${CC_SEARCH_FLAGS},-R,$v9gcclibdir" + +else case e in #( + e) if test "$arch" = "amd64 i386" +then : + + SHLIB_LD="$SHLIB_LD -m64 -static-libgcc" + +fi ;; +esac +fi + +fi + +else case e in #( + e) + if test "$use_sunmath" = yes +then : + textmode=textoff +else case e in #( + e) textmode=text ;; +esac +fi + case $system in + SunOS-5.[1-9][0-9]*|SunOS-5.[7-9]) + SHLIB_LD="\${CC} -G -z $textmode \${LDFLAGS}";; + *) + SHLIB_LD="/usr/ccs/bin/ld -G -z $textmode";; + esac + CC_SEARCH_FLAGS='-Wl,-R,${LIB_RUNTIME_DIR}' + LD_SEARCH_FLAGS='-R ${LIB_RUNTIME_DIR}' + ;; +esac +fi + ;; + UNIX_SV* | UnixWare-5*) + SHLIB_CFLAGS="-KPIC" + SHLIB_LD='${CC} -G' + SHLIB_LD_LIBS="" + SHLIB_SUFFIX=".so" + DL_OBJS="tclLoadDl.o" + DL_LIBS="-ldl" + # Some UNIX_SV* systems (unixware 1.1.2 for example) have linkers + # that don't grok the -Bexport option. Test that it does. + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for ld accepts -Bexport flag" >&5 +printf %s "checking for ld accepts -Bexport flag... " >&6; } +if test ${tcl_cv_ld_Bexport+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_ldflags=$LDFLAGS + LDFLAGS="$LDFLAGS -Wl,-Bexport" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ +int i; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_ld_Bexport=yes +else case e in #( + e) tcl_cv_ld_Bexport=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$hold_ldflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_ld_Bexport" >&5 +printf '%s\n' "$tcl_cv_ld_Bexport" >&6; } + if test $tcl_cv_ld_Bexport = yes +then : + + LDFLAGS="$LDFLAGS -Wl,-Bexport" + +fi + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + ;; + esac + + if test "$do64bit" = yes -a "$do64bit_ok" = no +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: 64bit support being disabled -- don't know magic for this platform" >&5 +printf '%s\n' "$as_me: WARNING: 64bit support being disabled -- don't know magic for this platform" >&2;} + +fi + + if test "$do64bit" = yes -a "$do64bit_ok" = yes +then : + + +printf '%s\n' "#define TCL_CFG_DO64BIT 1" >>confdefs.h + + +fi + + + + # Step 4: disable dynamic loading if requested via a command-line switch. + + # Check whether --enable-load was given. +if test ${enable_load+y} +then : + enableval=$enable_load; tcl_ok=$enableval +else case e in #( + e) tcl_ok=yes ;; +esac +fi + + if test "$tcl_ok" = no +then : + DL_OBJS="" +fi + + if test "x$DL_OBJS" != x +then : + BUILD_DLTEST="\$(DLTEST_TARGETS)" +else case e in #( + e) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: Can't figure out how to do dynamic loading or shared libraries on this system." >&5 +printf '%s\n' "$as_me: WARNING: Can't figure out how to do dynamic loading or shared libraries on this system." >&2;} + SHLIB_CFLAGS="" + SHLIB_LD="" + SHLIB_SUFFIX="" + DL_OBJS="tclLoadNone.o" + DL_LIBS="" + LDFLAGS="$LDFLAGS_ORIG" + CC_SEARCH_FLAGS="" + LD_SEARCH_FLAGS="" + BUILD_DLTEST="" + ;; +esac +fi + LDFLAGS="$LDFLAGS $LDFLAGS_ARCH" + + # If we're running gcc, then change the C flags for compiling shared + # libraries to the right flags for gcc, instead of those for the + # standard manufacturer compiler. + + if test "$DL_OBJS" != "tclLoadNone.o" -a "$GCC" = yes +then : + + case $system in + AIX-*) ;; + BSD/OS*) ;; + CYGWIN_*|MINGW32_*|MSYS_*) ;; + HP-UX*) ;; + Darwin-*) ;; + IRIX*) ;; + Linux*|GNU*) ;; + NetBSD-*|OpenBSD-*) ;; + OSF1-*) ;; + SCO_SV-3.2*) ;; + *) SHLIB_CFLAGS="-fPIC" ;; + esac +fi + + if test "$tcl_cv_cc_visibility_hidden" != yes +then : + + +printf '%s\n' "#define MODULE_SCOPE extern" >>confdefs.h + + +fi + + if test "$SHARED_LIB_SUFFIX" = "" +then : + + SHARED_LIB_SUFFIX='${VERSION}${SHLIB_SUFFIX}' +fi + if test "$UNSHARED_LIB_SUFFIX" = "" +then : + + UNSHARED_LIB_SUFFIX='${VERSION}.a' +fi + DLL_INSTALL_DIR="\$(LIB_INSTALL_DIR)" + + if test "${SHARED_BUILD}" = 1 -a "${SHLIB_SUFFIX}" != "" +then : + + LIB_SUFFIX=${SHARED_LIB_SUFFIX} + MAKE_LIB='${SHLIB_LD} -o $@ ${OBJS} ${LDFLAGS} ${SHLIB_LD_LIBS} ${TCL_SHLIB_LD_EXTRAS} ${TK_SHLIB_LD_EXTRAS} ${LD_SEARCH_FLAGS}' + if test "${SHLIB_SUFFIX}" = ".dll" +then : + + INSTALL_LIB='$(INSTALL_LIBRARY) $(LIB_FILE) "$(BIN_INSTALL_DIR)/$(LIB_FILE)"' + DLL_INSTALL_DIR="\$(BIN_INSTALL_DIR)" + +else case e in #( + e) + INSTALL_LIB='$(INSTALL_LIBRARY) $(LIB_FILE) "$(LIB_INSTALL_DIR)/$(LIB_FILE)"' + ;; +esac +fi + +else case e in #( + e) + LIB_SUFFIX=${UNSHARED_LIB_SUFFIX} + + if test "$RANLIB" = "" +then : + + MAKE_LIB='$(STLIB_LD) $@ ${OBJS}' + +else case e in #( + e) + MAKE_LIB='${STLIB_LD} $@ ${OBJS} ; ${RANLIB} $@' + ;; +esac +fi + INSTALL_LIB='$(INSTALL_LIBRARY) $(LIB_FILE) "$(LIB_INSTALL_DIR)/$(LIB_FILE)"' + ;; +esac +fi + + # Stub lib does not depend on shared/static configuration + if test "$RANLIB" = "" +then : + + MAKE_STUB_LIB='${STLIB_LD} $@ ${STUB_LIB_OBJS}' + +else case e in #( + e) + MAKE_STUB_LIB='${STLIB_LD} $@ ${STUB_LIB_OBJS} ; ${RANLIB} $@' + ;; +esac +fi + INSTALL_STUB_LIB='$(INSTALL_LIBRARY) $(STUB_LIB_FILE) "$(LIB_INSTALL_DIR)/$(STUB_LIB_FILE)"' + + # Define TCL_LIBS now that we know what DL_LIBS is. + # The trick here is that we don't want to change the value of TCL_LIBS if + # it is already set when tclConfig.sh had been loaded by Tk. + if test "x${TCL_LIBS}" = x +then : + + TCL_LIBS="${DL_LIBS} ${LIBS} ${MATH_LIBS}" +fi + + + # See if the compiler supports casting to a union type. + # This is used to stop gcc from printing a compiler + # warning when initializing a union member. + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for cast to union support" >&5 +printf %s "checking for cast to union support... " >&6; } +if test ${tcl_cv_cast_to_union+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + union foo { int i; double d; }; + union foo f = (union foo) (int) 0; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_cast_to_union=yes +else case e in #( + e) tcl_cv_cast_to_union=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cast_to_union" >&5 +printf '%s\n' "$tcl_cv_cast_to_union" >&6; } + if test "$tcl_cv_cast_to_union" = "yes"; then + +printf '%s\n' "#define HAVE_CAST_TO_UNION 1" >>confdefs.h + + fi + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -fno-lto" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for working -fno-lto" >&5 +printf %s "checking for working -fno-lto... " >&6; } +if test ${ac_cv_nolto+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_nolto=yes +else case e in #( + e) ac_cv_nolto=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_nolto" >&5 +printf '%s\n' "$ac_cv_nolto" >&6; } + CFLAGS=$hold_cflags + if test "$ac_cv_nolto" = "yes" ; then + CFLAGS_NOLTO="-fno-lto" + else + CFLAGS_NOLTO="" + fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if the compiler understands -finput-charset" >&5 +printf %s "checking if the compiler understands -finput-charset... " >&6; } +if test ${tcl_cv_cc_input_charset+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -finput-charset=UTF-8" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_cc_input_charset=yes +else case e in #( + e) tcl_cv_cc_input_charset=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_input_charset" >&5 +printf '%s\n' "$tcl_cv_cc_input_charset" >&6; } + if test $tcl_cv_cc_input_charset = yes; then + CFLAGS="$CFLAGS -finput-charset=UTF-8" + fi + + # Check for vfork, posix_spawnp() and friends unconditionally + ac_fn_c_check_func "$LINENO" "vfork" "ac_cv_func_vfork" +if test "x$ac_cv_func_vfork" = xyes +then : + printf '%s\n' "#define HAVE_VFORK 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "posix_spawnp" "ac_cv_func_posix_spawnp" +if test "x$ac_cv_func_posix_spawnp" = xyes +then : + printf '%s\n' "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_adddup2" "ac_cv_func_posix_spawn_file_actions_adddup2" +if test "x$ac_cv_func_posix_spawn_file_actions_adddup2" = xyes +then : + printf '%s\n' "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "posix_spawnattr_setflags" "ac_cv_func_posix_spawnattr_setflags" +if test "x$ac_cv_func_posix_spawnattr_setflags" = xyes +then : + printf '%s\n' "#define HAVE_POSIX_SPAWNATTR_SETFLAGS 1" >>confdefs.h + +fi + + + # FIXME: This subst was left in only because the TCL_DL_LIBS + # entry in tclConfig.sh uses it. It is not clear why someone + # would use TCL_DL_LIBS instead of TCL_LIBS. + + + + + + + + + + + + + + + + + + + + + + + + + + +cat >>confdefs.h <<_ACEOF +#define TCL_SHLIB_EXT "${SHLIB_SUFFIX}" +_ACEOF + + + + + + + + + + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for build with symbols" >&5 +printf %s "checking for build with symbols... " >&6; } + # Check whether --enable-symbols was given. +if test ${enable_symbols+y} +then : + enableval=$enable_symbols; tcl_ok=$enableval +else case e in #( + e) tcl_ok=no ;; +esac +fi + +# FIXME: Currently, LDFLAGS_DEFAULT is not used, it should work like CFLAGS_DEFAULT. + if test "$tcl_ok" = "no"; then + CFLAGS_DEFAULT='$(CFLAGS_OPTIMIZE)' + LDFLAGS_DEFAULT='$(LDFLAGS_OPTIMIZE)' + +printf '%s\n' "#define NDEBUG 1" >>confdefs.h + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } + +printf '%s\n' "#define TCL_CFG_OPTIMIZED 1" >>confdefs.h + + else + CFLAGS_DEFAULT='$(CFLAGS_DEBUG)' + LDFLAGS_DEFAULT='$(LDFLAGS_DEBUG)' + if test "$tcl_ok" = "yes"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes (standard debugging)" >&5 +printf '%s\n' "yes (standard debugging)" >&6; } + fi + fi + + + + if test "$tcl_ok" = "mem" -o "$tcl_ok" = "all"; then + +printf '%s\n' "#define TCL_MEM_DEBUG 1" >>confdefs.h + + fi + + if test "$tcl_ok" = "compile" -o "$tcl_ok" = "all"; then + +printf '%s\n' "#define TCL_COMPILE_DEBUG 1" >>confdefs.h + + +printf '%s\n' "#define TCL_COMPILE_STATS 1" >>confdefs.h + + fi + + if test "$tcl_ok" != "yes" -a "$tcl_ok" != "no"; then + if test "$tcl_ok" = "all"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: enabled symbols mem compile debugging" >&5 +printf '%s\n' "enabled symbols mem compile debugging" >&6; } + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: enabled $tcl_ok debugging" >&5 +printf '%s\n' "enabled $tcl_ok debugging" >&6; } + fi + fi + + + +printf '%s\n' "#define MP_PREC 4" >>confdefs.h + + +#-------------------------------------------------------------------- +# Detect what compiler flags to set for 64-bit support. +#-------------------------------------------------------------------- + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for required early compiler flags" >&5 +printf %s "checking for required early compiler flags... " >&6; } + tcl_flags="" + + if test ${tcl_cv_flag__isoc99_source+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +char *p = (char *)strtoll; char *q = (char *)strtoull; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_flag__isoc99_source=no +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _ISOC99_SOURCE 1 +#include +int +main (void) +{ +char *p = (char *)strtoll; char *q = (char *)strtoull; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_flag__isoc99_source=yes +else case e in #( + e) tcl_cv_flag__isoc99_source=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + + if test "x${tcl_cv_flag__isoc99_source}" = "xyes" ; then + +printf '%s\n' "#define _ISOC99_SOURCE 1" >>confdefs.h + + tcl_flags="$tcl_flags _ISOC99_SOURCE" + fi + + + if test ${tcl_cv_flag__file_offset_bits+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +switch (0) { case 0: case (sizeof(off_t)==sizeof(long long)): ; } + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_flag__file_offset_bits=no +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _FILE_OFFSET_BITS 64 +#include +int +main (void) +{ +switch (0) { case 0: case (sizeof(off_t)==sizeof(long long)): ; } + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_flag__file_offset_bits=yes +else case e in #( + e) tcl_cv_flag__file_offset_bits=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + + if test "x${tcl_cv_flag__file_offset_bits}" = "xyes" ; then + +printf '%s\n' "#define _FILE_OFFSET_BITS 64" >>confdefs.h + + tcl_flags="$tcl_flags _FILE_OFFSET_BITS" + fi + + + if test ${tcl_cv_flag__largefile64_source+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +struct stat64 buf; int i = stat64("/", &buf); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_flag__largefile64_source=no +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _LARGEFILE64_SOURCE 1 +#include +int +main (void) +{ +struct stat64 buf; int i = stat64("/", &buf); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_flag__largefile64_source=yes +else case e in #( + e) tcl_cv_flag__largefile64_source=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + + if test "x${tcl_cv_flag__largefile64_source}" = "xyes" ; then + +printf '%s\n' "#define _LARGEFILE64_SOURCE 1" >>confdefs.h + + tcl_flags="$tcl_flags _LARGEFILE64_SOURCE" + fi + + if test "x${tcl_flags}" = "x" ; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none" >&5 +printf '%s\n' "none" >&6; } + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: ${tcl_flags}" >&5 +printf '%s\n' "${tcl_flags}" >&6; } + fi + + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if 'long' and 'long long' have the same size (64-bit)?" >&5 +printf %s "checking if 'long' and 'long long' have the same size (64-bit)?... " >&6; } + if test ${tcl_cv_type_64bit+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + tcl_cv_type_64bit=none + # See if we could use long anyway Note that we substitute in the + # type that is our current guess for a 64-bit type inside this check + # program, so it should be modified only carefully... + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ +switch (0) { + case 1: case (sizeof(long long)==sizeof(long)): ; + } + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_type_64bit="long long" +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + + if test "${tcl_cv_type_64bit}" = none ; then + +printf '%s\n' "#define TCL_WIDE_INT_IS_LONG 1" >>confdefs.h + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } + # Now check for auxiliary declarations + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for 64-bit time_t" >&5 +printf %s "checking for 64-bit time_t... " >&6; } +if test ${tcl_cv_time_t_64+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +switch (0) {case 0: case (sizeof(time_t)==sizeof(long long)): ;} + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_time_t_64=yes +else case e in #( + e) tcl_cv_time_t_64=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_time_t_64" >&5 +printf '%s\n' "$tcl_cv_time_t_64" >&6; } + if test "x${tcl_cv_time_t_64}" = "xno" ; then + # Note that _TIME_BITS=64 requires _FILE_OFFSET_BITS=64 + # which SC_TCL_EARLY_FLAGS has defined if necessary. + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if _TIME_BITS=64 enables 64-bit time_t" >&5 +printf %s "checking if _TIME_BITS=64 enables 64-bit time_t... " >&6; } +if test ${tcl_cv__time_bits+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#define _TIME_BITS 64 +#include +int +main (void) +{ +switch (0) {case 0: case (sizeof(time_t)==sizeof(long long)): ;} + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv__time_bits=yes +else case e in #( + e) tcl_cv__time_bits=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv__time_bits" >&5 +printf '%s\n' "$tcl_cv__time_bits" >&6; } + if test "x${tcl_cv__time_bits}" = "xyes" ; then + +printf '%s\n' "#define _TIME_BITS 64" >>confdefs.h + + fi + fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for struct dirent64" >&5 +printf %s "checking for struct dirent64... " >&6; } +if test ${tcl_cv_struct_dirent64+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +int +main (void) +{ +struct dirent64 p; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_struct_dirent64=yes +else case e in #( + e) tcl_cv_struct_dirent64=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_struct_dirent64" >&5 +printf '%s\n' "$tcl_cv_struct_dirent64" >&6; } + if test "x${tcl_cv_struct_dirent64}" = "xyes" ; then + +printf '%s\n' "#define HAVE_STRUCT_DIRENT64 1" >>confdefs.h + + fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for DIR64" >&5 +printf %s "checking for DIR64... " >&6; } +if test ${tcl_cv_DIR64+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +int +main (void) +{ +struct dirent64 *p; DIR64 d = opendir64("."); + p = readdir64(d); rewinddir64(d); closedir64(d); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_DIR64=yes +else case e in #( + e) tcl_cv_DIR64=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_DIR64" >&5 +printf '%s\n' "$tcl_cv_DIR64" >&6; } + if test "x${tcl_cv_DIR64}" = "xyes" ; then + +printf '%s\n' "#define HAVE_DIR64 1" >>confdefs.h + + fi + + ac_fn_c_check_func "$LINENO" "open64" "ac_cv_func_open64" +if test "x$ac_cv_func_open64" = xyes +then : + printf '%s\n' "#define HAVE_OPEN64 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "lseek64" "ac_cv_func_lseek64" +if test "x$ac_cv_func_lseek64" = xyes +then : + printf '%s\n' "#define HAVE_LSEEK64 1" >>confdefs.h + +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for off64_t" >&5 +printf %s "checking for off64_t... " >&6; } + if test ${tcl_cv_type_off64_t+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +off64_t offset; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_type_off64_t=yes +else case e in #( + e) tcl_cv_type_off64_t=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + + if test "x${tcl_cv_type_off64_t}" = "xyes" && \ + test "x${ac_cv_func_lseek64}" = "xyes" && \ + test "x${ac_cv_func_open64}" = "xyes" ; then + +printf '%s\n' "#define HAVE_TYPE_OFF64_T 1" >>confdefs.h + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } + fi + fi + + +#-------------------------------------------------------------------- +# Check endianness because we can optimize comparisons of +# Tcl_UniChar strings to memcmp on big-endian systems. +#-------------------------------------------------------------------- + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5 +printf %s "checking whether byte ordering is bigendian... " >&6; } +if test ${ac_cv_c_bigendian+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_c_bigendian=unknown + # See if we're dealing with a universal compiler. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifndef __APPLE_CC__ + not a universal capable compiler + #endif + typedef int dummy; + +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + + # Check for potential -arch flags. It is not universal unless + # there are at least two -arch flags with different values. + ac_arch= + ac_prev= + for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do + if test -n "$ac_prev"; then + case $ac_word in + i?86 | x86_64 | ppc | ppc64) + if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then + ac_arch=$ac_word + else + ac_cv_c_bigendian=universal + break + fi + ;; + esac + ac_prev= + elif test "x$ac_word" = "x-arch"; then + ac_prev=arch + fi + done +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + if test $ac_cv_c_bigendian = unknown; then + # See if sys/param.h defines the BYTE_ORDER macro. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + #include + +int +main (void) +{ +#if ! (defined BYTE_ORDER && defined BIG_ENDIAN \\ + && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \\ + && LITTLE_ENDIAN) + bogus endian macros + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + # It does; now see whether it defined to BIG_ENDIAN or not. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + #include + +int +main (void) +{ +#if BYTE_ORDER != BIG_ENDIAN + not big endian + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_c_bigendian=yes +else case e in #( + e) ac_cv_c_bigendian=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + fi + if test $ac_cv_c_bigendian = unknown; then + # See if defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris). + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +int +main (void) +{ +#if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN) + bogus endian macros + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + # It does; now see whether it defined to _BIG_ENDIAN or not. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +int +main (void) +{ +#ifndef _BIG_ENDIAN + not big endian + #endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_c_bigendian=yes +else case e in #( + e) ac_cv_c_bigendian=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + fi + if test $ac_cv_c_bigendian = unknown; then + # Compile a test program. + if test "$cross_compiling" = yes +then : + # Try to guess by grepping values from an object file. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +unsigned short int ascii_mm[] = + { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 }; + unsigned short int ascii_ii[] = + { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 }; + int use_ascii (int i) { + return ascii_mm[i] + ascii_ii[i]; + } + unsigned short int ebcdic_ii[] = + { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 }; + unsigned short int ebcdic_mm[] = + { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 }; + int use_ebcdic (int i) { + return ebcdic_mm[i] + ebcdic_ii[i]; + } + int + main (int argc, char **argv) + { + /* Intimidate the compiler so that it does not + optimize the arrays away. */ + char *p = argv[0]; + ascii_mm[1] = *p++; ebcdic_mm[1] = *p++; + ascii_ii[1] = *p++; ebcdic_ii[1] = *p++; + return use_ascii (argc) == use_ebcdic (*p); + } +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + if grep BIGenDianSyS conftest$ac_exeext >/dev/null; then + ac_cv_c_bigendian=yes + fi + if grep LiTTleEnDian conftest$ac_exeext >/dev/null ; then + if test "$ac_cv_c_bigendian" = unknown; then + ac_cv_c_bigendian=no + else + # finding both strings is unlikely to happen, but who knows? + ac_cv_c_bigendian=unknown + fi + fi +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_includes_default +int +main (void) +{ + + /* Are we little or big endian? From Harbison&Steele. */ + union + { + long int l; + char c[sizeof (long int)]; + } u; + u.l = 1; + return u.c[sizeof (long int) - 1] == 1; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO" +then : + ac_cv_c_bigendian=no +else case e in #( + e) ac_cv_c_bigendian=yes ;; +esac +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + + fi ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5 +printf '%s\n' "$ac_cv_c_bigendian" >&6; } + case $ac_cv_c_bigendian in #( + yes) + printf '%s\n' "#define WORDS_BIGENDIAN 1" >>confdefs.h +;; #( + no) + ;; #( + universal) + # + ;; #( + *) + as_fn_error $? "unknown endianness + presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;; + esac + + +#-------------------------------------------------------------------- +# Supply substitutes for missing POSIX library procedures, or +# set flags so Tcl uses alternate procedures. +#-------------------------------------------------------------------- + +# Check if Posix compliant getcwd exists, if not we'll use getwd. + + for ac_func in getcwd +do : + ac_fn_c_check_func "$LINENO" "getcwd" "ac_cv_func_getcwd" +if test "x$ac_cv_func_getcwd" = xyes +then : + printf '%s\n' "#define HAVE_GETCWD 1" >>confdefs.h + +else case e in #( + e) +printf '%s\n' "#define USEGETWD 1" >>confdefs.h + ;; +esac +fi + +done +# Nb: if getcwd uses popen and pwd(1) (like SunOS 4) we should really +# define USEGETWD even if the posix getcwd exists. Add a test ? + +ac_fn_c_check_func "$LINENO" "mkstemp" "ac_cv_func_mkstemp" +if test "x$ac_cv_func_mkstemp" = xyes +then : + printf '%s\n' "#define HAVE_MKSTEMP 1" >>confdefs.h + +else case e in #( + e) case " $LIBOBJS " in + *" mkstemp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS mkstemp.$ac_objext" + ;; +esac + ;; +esac +fi +ac_fn_c_check_func "$LINENO" "waitpid" "ac_cv_func_waitpid" +if test "x$ac_cv_func_waitpid" = xyes +then : + printf '%s\n' "#define HAVE_WAITPID 1" >>confdefs.h + +else case e in #( + e) case " $LIBOBJS " in + *" waitpid.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS waitpid.$ac_objext" + ;; +esac + ;; +esac +fi + +ac_fn_c_check_func "$LINENO" "strerror" "ac_cv_func_strerror" +if test "x$ac_cv_func_strerror" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_STRERROR 1" >>confdefs.h + ;; +esac +fi + +ac_fn_c_check_func "$LINENO" "getwd" "ac_cv_func_getwd" +if test "x$ac_cv_func_getwd" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_GETWD 1" >>confdefs.h + ;; +esac +fi + +ac_fn_c_check_func "$LINENO" "wait3" "ac_cv_func_wait3" +if test "x$ac_cv_func_wait3" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_WAIT3 1" >>confdefs.h + ;; +esac +fi + +ac_fn_c_check_func "$LINENO" "fork" "ac_cv_func_fork" +if test "x$ac_cv_func_fork" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_FORK 1" >>confdefs.h + ;; +esac +fi + +ac_fn_c_check_func "$LINENO" "mknod" "ac_cv_func_mknod" +if test "x$ac_cv_func_mknod" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_MKNOD 1" >>confdefs.h + ;; +esac +fi + +ac_fn_c_check_func "$LINENO" "tcdrain" "ac_cv_func_tcdrain" +if test "x$ac_cv_func_tcdrain" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_TCDRAIN 1" >>confdefs.h + ;; +esac +fi + +ac_fn_c_check_func "$LINENO" "uname" "ac_cv_func_uname" +if test "x$ac_cv_func_uname" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_UNAME 1" >>confdefs.h + ;; +esac +fi + + +if test "`uname -s`" = "Darwin" && \ + test "`uname -r | awk -F. '{print $1}'`" -lt 7; then + # prior to Darwin 7, realpath is not threadsafe, so don't + # use it when threads are enabled, c.f. bug # 711232 + ac_cv_func_realpath=no +fi +ac_fn_c_check_func "$LINENO" "realpath" "ac_cv_func_realpath" +if test "x$ac_cv_func_realpath" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_REALPATH 1" >>confdefs.h + ;; +esac +fi + + + + NEED_FAKE_RFC2553=0 + + for ac_func in getnameinfo getaddrinfo freeaddrinfo gai_strerror +do : + as_ac_var=`printf '%s\n' "ac_cv_func_$ac_func" | sed "$as_sed_sh"` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes" +then : + cat >>confdefs.h <<_ACEOF +#define `printf '%s\n' "HAVE_$ac_func" | sed "$as_sed_cpp"` 1 +_ACEOF + +else case e in #( + e) NEED_FAKE_RFC2553=1 ;; +esac +fi + +done + ac_fn_c_check_type "$LINENO" "struct addrinfo" "ac_cv_type_struct_addrinfo" " +#include +#include +#include +#include + +" +if test "x$ac_cv_type_struct_addrinfo" = xyes +then : + +printf '%s\n' "#define HAVE_STRUCT_ADDRINFO 1" >>confdefs.h + + +else case e in #( + e) NEED_FAKE_RFC2553=1 ;; +esac +fi +ac_fn_c_check_type "$LINENO" "struct in6_addr" "ac_cv_type_struct_in6_addr" " +#include +#include +#include +#include + +" +if test "x$ac_cv_type_struct_in6_addr" = xyes +then : + +printf '%s\n' "#define HAVE_STRUCT_IN6_ADDR 1" >>confdefs.h + + +else case e in #( + e) NEED_FAKE_RFC2553=1 ;; +esac +fi +ac_fn_c_check_type "$LINENO" "struct sockaddr_in6" "ac_cv_type_struct_sockaddr_in6" " +#include +#include +#include +#include + +" +if test "x$ac_cv_type_struct_sockaddr_in6" = xyes +then : + +printf '%s\n' "#define HAVE_STRUCT_SOCKADDR_IN6 1" >>confdefs.h + + +else case e in #( + e) NEED_FAKE_RFC2553=1 ;; +esac +fi +ac_fn_c_check_type "$LINENO" "struct sockaddr_storage" "ac_cv_type_struct_sockaddr_storage" " +#include +#include +#include +#include + +" +if test "x$ac_cv_type_struct_sockaddr_storage" = xyes +then : + +printf '%s\n' "#define HAVE_STRUCT_SOCKADDR_STORAGE 1" >>confdefs.h + + +else case e in #( + e) NEED_FAKE_RFC2553=1 ;; +esac +fi + +if test "x$NEED_FAKE_RFC2553" = "x1"; then + +printf '%s\n' "#define NEED_FAKE_RFC2553 1" >>confdefs.h + + case " $LIBOBJS " in + *" fake-rfc2553.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS fake-rfc2553.$ac_objext" + ;; +esac + + ac_fn_c_check_func "$LINENO" "strlcpy" "ac_cv_func_strlcpy" +if test "x$ac_cv_func_strlcpy" = xyes +then : + +fi + +fi + + +#-------------------------------------------------------------------- +# Look for thread-safe variants of some library functions. +#-------------------------------------------------------------------- + +ac_fn_c_check_func "$LINENO" "getpwuid_r" "ac_cv_func_getpwuid_r" +if test "x$ac_cv_func_getpwuid_r" = xyes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for getpwuid_r with 5 args" >&5 +printf %s "checking for getpwuid_r with 5 args... " >&6; } +if test ${tcl_cv_api_getpwuid_r_5+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main (void) +{ + + uid_t uid; + struct passwd pw, *pwp; + char buf[512]; + int buflen = 512; + + (void) getpwuid_r(uid, &pw, buf, buflen, &pwp); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_getpwuid_r_5=yes +else case e in #( + e) tcl_cv_api_getpwuid_r_5=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getpwuid_r_5" >&5 +printf '%s\n' "$tcl_cv_api_getpwuid_r_5" >&6; } + tcl_ok=$tcl_cv_api_getpwuid_r_5 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETPWUID_R_5 1" >>confdefs.h + + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for getpwuid_r with 4 args" >&5 +printf %s "checking for getpwuid_r with 4 args... " >&6; } +if test ${tcl_cv_api_getpwuid_r_4+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main (void) +{ + + uid_t uid; + struct passwd pw; + char buf[512]; + int buflen = 512; + + (void)getpwnam_r(uid, &pw, buf, buflen); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_getpwuid_r_4=yes +else case e in #( + e) tcl_cv_api_getpwuid_r_4=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getpwuid_r_4" >&5 +printf '%s\n' "$tcl_cv_api_getpwuid_r_4" >&6; } + tcl_ok=$tcl_cv_api_getpwuid_r_4 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETPWUID_R_4 1" >>confdefs.h + + fi + fi + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETPWUID_R 1" >>confdefs.h + + fi + +fi + +ac_fn_c_check_func "$LINENO" "getpwnam_r" "ac_cv_func_getpwnam_r" +if test "x$ac_cv_func_getpwnam_r" = xyes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for getpwnam_r with 5 args" >&5 +printf %s "checking for getpwnam_r with 5 args... " >&6; } +if test ${tcl_cv_api_getpwnam_r_5+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main (void) +{ + + char *name; + struct passwd pw, *pwp; + char buf[512]; + int buflen = 512; + + (void) getpwnam_r(name, &pw, buf, buflen, &pwp); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_getpwnam_r_5=yes +else case e in #( + e) tcl_cv_api_getpwnam_r_5=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getpwnam_r_5" >&5 +printf '%s\n' "$tcl_cv_api_getpwnam_r_5" >&6; } + tcl_ok=$tcl_cv_api_getpwnam_r_5 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETPWNAM_R_5 1" >>confdefs.h + + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for getpwnam_r with 4 args" >&5 +printf %s "checking for getpwnam_r with 4 args... " >&6; } +if test ${tcl_cv_api_getpwnam_r_4+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main (void) +{ + + char *name; + struct passwd pw; + char buf[512]; + int buflen = 512; + + (void)getpwnam_r(name, &pw, buf, buflen); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_getpwnam_r_4=yes +else case e in #( + e) tcl_cv_api_getpwnam_r_4=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getpwnam_r_4" >&5 +printf '%s\n' "$tcl_cv_api_getpwnam_r_4" >&6; } + tcl_ok=$tcl_cv_api_getpwnam_r_4 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETPWNAM_R_4 1" >>confdefs.h + + fi + fi + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETPWNAM_R 1" >>confdefs.h + + fi + +fi + +ac_fn_c_check_func "$LINENO" "getgrgid_r" "ac_cv_func_getgrgid_r" +if test "x$ac_cv_func_getgrgid_r" = xyes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for getgrgid_r with 5 args" >&5 +printf %s "checking for getgrgid_r with 5 args... " >&6; } +if test ${tcl_cv_api_getgrgid_r_5+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main (void) +{ + + gid_t gid; + struct group gr, *grp; + char buf[512]; + int buflen = 512; + + (void) getgrgid_r(gid, &gr, buf, buflen, &grp); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_getgrgid_r_5=yes +else case e in #( + e) tcl_cv_api_getgrgid_r_5=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getgrgid_r_5" >&5 +printf '%s\n' "$tcl_cv_api_getgrgid_r_5" >&6; } + tcl_ok=$tcl_cv_api_getgrgid_r_5 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETGRGID_R_5 1" >>confdefs.h + + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for getgrgid_r with 4 args" >&5 +printf %s "checking for getgrgid_r with 4 args... " >&6; } +if test ${tcl_cv_api_getgrgid_r_4+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main (void) +{ + + gid_t gid; + struct group gr; + char buf[512]; + int buflen = 512; + + (void)getgrgid_r(gid, &gr, buf, buflen); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_getgrgid_r_4=yes +else case e in #( + e) tcl_cv_api_getgrgid_r_4=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getgrgid_r_4" >&5 +printf '%s\n' "$tcl_cv_api_getgrgid_r_4" >&6; } + tcl_ok=$tcl_cv_api_getgrgid_r_4 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETGRGID_R_4 1" >>confdefs.h + + fi + fi + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETGRGID_R 1" >>confdefs.h + + fi + +fi + +ac_fn_c_check_func "$LINENO" "getgrnam_r" "ac_cv_func_getgrnam_r" +if test "x$ac_cv_func_getgrnam_r" = xyes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for getgrnam_r with 5 args" >&5 +printf %s "checking for getgrnam_r with 5 args... " >&6; } +if test ${tcl_cv_api_getgrnam_r_5+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main (void) +{ + + char *name; + struct group gr, *grp; + char buf[512]; + int buflen = 512; + + (void) getgrnam_r(name, &gr, buf, buflen, &grp); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_getgrnam_r_5=yes +else case e in #( + e) tcl_cv_api_getgrnam_r_5=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getgrnam_r_5" >&5 +printf '%s\n' "$tcl_cv_api_getgrnam_r_5" >&6; } + tcl_ok=$tcl_cv_api_getgrnam_r_5 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETGRNAM_R_5 1" >>confdefs.h + + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for getgrnam_r with 4 args" >&5 +printf %s "checking for getgrnam_r with 4 args... " >&6; } +if test ${tcl_cv_api_getgrnam_r_4+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main (void) +{ + + char *name; + struct group gr; + char buf[512]; + int buflen = 512; + + (void)getgrnam_r(name, &gr, buf, buflen); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_getgrnam_r_4=yes +else case e in #( + e) tcl_cv_api_getgrnam_r_4=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_getgrnam_r_4" >&5 +printf '%s\n' "$tcl_cv_api_getgrnam_r_4" >&6; } + tcl_ok=$tcl_cv_api_getgrnam_r_4 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETGRNAM_R_4 1" >>confdefs.h + + fi + fi + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETGRNAM_R 1" >>confdefs.h + + fi + +fi + +if test "`uname -s`" = "Darwin" && \ + test "`uname -r | awk -F. '{print $1}'`" -gt 5; then + # Starting with Darwin 6 (Mac OSX 10.2), gethostbyX + # are actually MT-safe as they always return pointers + # from TSD instead of static storage. + +printf '%s\n' "#define HAVE_MTSAFE_GETHOSTBYNAME 1" >>confdefs.h + + +printf '%s\n' "#define HAVE_MTSAFE_GETHOSTBYADDR 1" >>confdefs.h + + +elif test "`uname -s`" = "HP-UX" && \ + test "`uname -r|sed -e 's|B\.||' -e 's|\..*$||'`" -gt 10; then + # Starting with HPUX 11.00 (we believe), gethostbyX + # are actually MT-safe as they always return pointers + # from TSD instead of static storage. + +printf '%s\n' "#define HAVE_MTSAFE_GETHOSTBYNAME 1" >>confdefs.h + + +printf '%s\n' "#define HAVE_MTSAFE_GETHOSTBYADDR 1" >>confdefs.h + + +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC options to detect undeclared functions" >&5 +printf %s "checking for $CC options to detect undeclared functions... " >&6; } +if test ${ac_cv_c_undeclared_builtin_options+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_save_CFLAGS=$CFLAGS + ac_cv_c_undeclared_builtin_options='cannot detect' + for ac_arg in '' -fno-builtin; do + CFLAGS="$ac_save_CFLAGS $ac_arg" + # This test program should *not* compile successfully. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ +(void) strchr; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + +else case e in #( + e) # This test program should compile successfully. + # No library function is consistently available on + # freestanding implementations, so test against a dummy + # declaration. Include always-available headers on the + # off chance that they somehow elicit warnings. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include +extern void ac_decl (int, char *); + +int +main (void) +{ +(void) ac_decl (0, (char *) 0); + (void) ac_decl; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + if test x"$ac_arg" = x +then : + ac_cv_c_undeclared_builtin_options='none needed' +else case e in #( + e) ac_cv_c_undeclared_builtin_options=$ac_arg ;; +esac +fi + break +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + done + CFLAGS=$ac_save_CFLAGS + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_undeclared_builtin_options" >&5 +printf '%s\n' "$ac_cv_c_undeclared_builtin_options" >&6; } + case $ac_cv_c_undeclared_builtin_options in #( + 'cannot detect') : + { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "cannot make $CC report undeclared builtins +See 'config.log' for more details" "$LINENO" 5; } ;; #( + 'none needed') : + ac_c_undeclared_builtin_options='' ;; #( + *) : + ac_c_undeclared_builtin_options=$ac_cv_c_undeclared_builtin_options ;; +esac + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC options to ignore future-version functions" >&5 +printf %s "checking for $CC options to ignore future-version functions... " >&6; } +if test ${ac_cv_c_future_darwin_options+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_compile_saved="$ac_compile" + ac_compile="$ac_compile -Werror=unguarded-availability-new" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#if ! (defined __APPLE__ && defined __MACH__) + #error "-Werror=unguarded-availability-new not needed here" + #endif + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_c_future_darwin_options='-Werror=unguarded-availability-new' +else case e in #( + e) ac_cv_c_future_darwin_options='none needed' ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ac_compile="$ac_compile_saved" + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_future_darwin_options" >&5 +printf '%s\n' "$ac_cv_c_future_darwin_options" >&6; } + case $ac_cv_c_future_darwin_options in #( + 'none needed') : + ac_c_future_darwin_options='' ;; #( + *) : + ac_c_future_darwin_options=$ac_cv_c_future_darwin_options ;; +esac + + + # Avoids picking hidden internal symbol from libc + ac_fn_check_decl "$LINENO" "gethostbyname_r" "ac_cv_have_decl_gethostbyname_r" "#include +" "$ac_c_undeclared_builtin_options$ac_c_future_darwin_options" "CFLAGS" +if test "x$ac_cv_have_decl_gethostbyname_r" = xyes +then : + ac_have_decl=1 +else case e in #( + e) ac_have_decl=0 ;; +esac +fi +printf '%s\n' "#define HAVE_DECL_GETHOSTBYNAME_R $ac_have_decl" >>confdefs.h +if test $ac_have_decl = 1 +then : + + tcl_cv_api_gethostbyname_r=yes +else case e in #( + e) tcl_cv_api_gethostbyname_r=no ;; +esac +fi + + + + if test "$tcl_cv_api_gethostbyname_r" = yes; then + ac_fn_c_check_func "$LINENO" "gethostbyname_r" "ac_cv_func_gethostbyname_r" +if test "x$ac_cv_func_gethostbyname_r" = xyes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for gethostbyname_r with 6 args" >&5 +printf %s "checking for gethostbyname_r with 6 args... " >&6; } +if test ${tcl_cv_api_gethostbyname_r_6+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main (void) +{ + + char *name; + struct hostent *he, *res; + char buffer[2048]; + int buflen = 2048; + int h_errnop; + + (void) gethostbyname_r(name, he, buffer, buflen, &res, &h_errnop); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_gethostbyname_r_6=yes +else case e in #( + e) tcl_cv_api_gethostbyname_r_6=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyname_r_6" >&5 +printf '%s\n' "$tcl_cv_api_gethostbyname_r_6" >&6; } + tcl_ok=$tcl_cv_api_gethostbyname_r_6 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETHOSTBYNAME_R_6 1" >>confdefs.h + + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for gethostbyname_r with 5 args" >&5 +printf %s "checking for gethostbyname_r with 5 args... " >&6; } +if test ${tcl_cv_api_gethostbyname_r_5+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main (void) +{ + + char *name; + struct hostent *he; + char buffer[2048]; + int buflen = 2048; + int h_errnop; + + (void) gethostbyname_r(name, he, buffer, buflen, &h_errnop); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_gethostbyname_r_5=yes +else case e in #( + e) tcl_cv_api_gethostbyname_r_5=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyname_r_5" >&5 +printf '%s\n' "$tcl_cv_api_gethostbyname_r_5" >&6; } + tcl_ok=$tcl_cv_api_gethostbyname_r_5 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETHOSTBYNAME_R_5 1" >>confdefs.h + + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for gethostbyname_r with 3 args" >&5 +printf %s "checking for gethostbyname_r with 3 args... " >&6; } +if test ${tcl_cv_api_gethostbyname_r_3+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main (void) +{ + + char *name; + struct hostent *he; + struct hostent_data data; + + (void) gethostbyname_r(name, he, &data); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_gethostbyname_r_3=yes +else case e in #( + e) tcl_cv_api_gethostbyname_r_3=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyname_r_3" >&5 +printf '%s\n' "$tcl_cv_api_gethostbyname_r_3" >&6; } + tcl_ok=$tcl_cv_api_gethostbyname_r_3 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETHOSTBYNAME_R_3 1" >>confdefs.h + + fi + fi + fi + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETHOSTBYNAME_R 1" >>confdefs.h + + fi + +fi + + fi + + + # Avoids picking hidden internal symbol from libc + ac_fn_check_decl "$LINENO" "gethostbyaddr_r" "ac_cv_have_decl_gethostbyaddr_r" "#include +" "$ac_c_undeclared_builtin_options$ac_c_future_darwin_options" "CFLAGS" +if test "x$ac_cv_have_decl_gethostbyaddr_r" = xyes +then : + ac_have_decl=1 +else case e in #( + e) ac_have_decl=0 ;; +esac +fi +printf '%s\n' "#define HAVE_DECL_GETHOSTBYADDR_R $ac_have_decl" >>confdefs.h +if test $ac_have_decl = 1 +then : + + tcl_cv_api_gethostbyaddr_r=yes +else case e in #( + e) tcl_cv_api_gethostbyaddr_r=no ;; +esac +fi + + + + if test "$tcl_cv_api_gethostbyaddr_r" = yes; then + ac_fn_c_check_func "$LINENO" "gethostbyaddr_r" "ac_cv_func_gethostbyaddr_r" +if test "x$ac_cv_func_gethostbyaddr_r" = xyes +then : + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for gethostbyaddr_r with 7 args" >&5 +printf %s "checking for gethostbyaddr_r with 7 args... " >&6; } +if test ${tcl_cv_api_gethostbyaddr_r_7+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main (void) +{ + + char *addr; + int length; + int type; + struct hostent *result; + char buffer[2048]; + int buflen = 2048; + int h_errnop; + + (void) gethostbyaddr_r(addr, length, type, result, buffer, buflen, + &h_errnop); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_gethostbyaddr_r_7=yes +else case e in #( + e) tcl_cv_api_gethostbyaddr_r_7=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyaddr_r_7" >&5 +printf '%s\n' "$tcl_cv_api_gethostbyaddr_r_7" >&6; } + tcl_ok=$tcl_cv_api_gethostbyaddr_r_7 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETHOSTBYADDR_R_7 1" >>confdefs.h + + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for gethostbyaddr_r with 8 args" >&5 +printf %s "checking for gethostbyaddr_r with 8 args... " >&6; } +if test ${tcl_cv_api_gethostbyaddr_r_8+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + +int +main (void) +{ + + char *addr; + int length; + int type; + struct hostent *result, *resultp; + char buffer[2048]; + int buflen = 2048; + int h_errnop; + + (void) gethostbyaddr_r(addr, length, type, result, buffer, buflen, + &resultp, &h_errnop); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_api_gethostbyaddr_r_8=yes +else case e in #( + e) tcl_cv_api_gethostbyaddr_r_8=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_gethostbyaddr_r_8" >&5 +printf '%s\n' "$tcl_cv_api_gethostbyaddr_r_8" >&6; } + tcl_ok=$tcl_cv_api_gethostbyaddr_r_8 + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETHOSTBYADDR_R_8 1" >>confdefs.h + + fi + fi + if test "$tcl_ok" = yes; then + +printf '%s\n' "#define HAVE_GETHOSTBYADDR_R 1" >>confdefs.h + + fi + +fi + + fi + +fi + +#--------------------------------------------------------------------------- +# Check for serial port interface. +# +# termios.h is present on all POSIX systems. +# sys/ioctl.h is almost always present, though what it contains +# is system-specific. +# sys/modem.h is needed on HP-UX. +#--------------------------------------------------------------------------- + +ac_fn_c_check_header_compile "$LINENO" "termios.h" "ac_cv_header_termios_h" "$ac_includes_default" +if test "x$ac_cv_header_termios_h" = xyes +then : + printf '%s\n' "#define HAVE_TERMIOS_H 1" >>confdefs.h + +fi + +ac_fn_c_check_header_compile "$LINENO" "sys/ioctl.h" "ac_cv_header_sys_ioctl_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_ioctl_h" = xyes +then : + printf '%s\n' "#define HAVE_SYS_IOCTL_H 1" >>confdefs.h + +fi + +ac_fn_c_check_header_compile "$LINENO" "sys/modem.h" "ac_cv_header_sys_modem_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_modem_h" = xyes +then : + printf '%s\n' "#define HAVE_SYS_MODEM_H 1" >>confdefs.h + +fi + + +#-------------------------------------------------------------------- +# Include sys/select.h if it exists and if it supplies things +# that appear to be useful and aren't already in sys/types.h. +# This appears to be true only on the RS/6000 under AIX. Some +# systems like OSF/1 have a sys/select.h that's of no use, and +# other systems like SCO UNIX have a sys/select.h that's +# pernicious. If "fd_set" isn't defined anywhere then set a +# special flag. +#-------------------------------------------------------------------- + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for fd_set in sys/types" >&5 +printf %s "checking for fd_set in sys/types... " >&6; } +if test ${tcl_cv_type_fd_set+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +fd_set readMask, writeMask; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_type_fd_set=yes +else case e in #( + e) tcl_cv_type_fd_set=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_type_fd_set" >&5 +printf '%s\n' "$tcl_cv_type_fd_set" >&6; } +tcl_ok=$tcl_cv_type_fd_set +if test $tcl_ok = no; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for fd_mask in sys/select" >&5 +printf %s "checking for fd_mask in sys/select... " >&6; } +if test ${tcl_cv_grep_fd_mask+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP_TRADITIONAL "fd_mask" >/dev/null 2>&1 +then : + tcl_cv_grep_fd_mask=present +else case e in #( + e) tcl_cv_grep_fd_mask=missing ;; +esac +fi +rm -rf conftest* + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_grep_fd_mask" >&5 +printf '%s\n' "$tcl_cv_grep_fd_mask" >&6; } + if test $tcl_cv_grep_fd_mask = present; then + +printf '%s\n' "#define HAVE_SYS_SELECT_H 1" >>confdefs.h + + tcl_ok=yes + fi +fi +if test $tcl_ok = no; then + +printf '%s\n' "#define NO_FD_SET 1" >>confdefs.h + +fi + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for pselect" >&5 +printf %s "checking for pselect... " >&6; } +if test ${tcl_cv_func_pselect+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +void *func = pselect; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_func_pselect=yes +else case e in #( + e) tcl_cv_func_pselect=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_func_pselect" >&5 +printf '%s\n' "$tcl_cv_func_pselect" >&6; } +tcl_ok=$tcl_cv_func_pselect +if test $tcl_ok = yes; then + +printf '%s\n' "#define HAVE_PSELECT 1" >>confdefs.h + +fi + +#------------------------------------------------------------------------ +# Options for the notifier. Checks for epoll(7) on Linux, and +# kqueue(2) on {DragonFly,Free,Net,Open}BSD +#------------------------------------------------------------------------ + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for advanced notifier support" >&5 +printf %s "checking for advanced notifier support... " >&6; } +case x`uname -s` in + xLinux) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: epoll(7)" >&5 +printf '%s\n' "epoll(7)" >&6; } + for ac_header in sys/epoll.h +do : + ac_fn_c_check_header_compile "$LINENO" "sys/epoll.h" "ac_cv_header_sys_epoll_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_epoll_h" = xyes +then : + printf '%s\n' "#define HAVE_SYS_EPOLL_H 1" >>confdefs.h + +printf '%s\n' "#define NOTIFIER_EPOLL 1" >>confdefs.h + +fi + +done + for ac_header in sys/eventfd.h +do : + ac_fn_c_check_header_compile "$LINENO" "sys/eventfd.h" "ac_cv_header_sys_eventfd_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_eventfd_h" = xyes +then : + printf '%s\n' "#define HAVE_SYS_EVENTFD_H 1" >>confdefs.h + +printf '%s\n' "#define HAVE_EVENTFD 1" >>confdefs.h + +fi + +done;; + xDragonFlyBSD|xFreeBSD|xNetBSD|xOpenBSD) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: kqueue(2)" >&5 +printf '%s\n' "kqueue(2)" >&6; } + # Messy because we want to check if *all* the headers are present, and not + # just *any* + tcl_kqueue_headers=x + for ac_header in sys/types.h sys/event.h sys/time.h +do : + as_ac_Header=`printf '%s\n' "ac_cv_header_$ac_header" | sed "$as_sed_sh"` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes" +then : + cat >>confdefs.h <<_ACEOF +#define `printf '%s\n' "HAVE_$ac_header" | sed "$as_sed_cpp"` 1 +_ACEOF + tcl_kqueue_headers=${tcl_kqueue_headers}y +fi + +done + if test $tcl_kqueue_headers = xyyy +then : + + +printf '%s\n' "#define NOTIFIER_KQUEUE 1" >>confdefs.h + +fi;; + xDarwin) + # Assume that we've got CoreFoundation present (checked elsewhere because + # of wider impact). + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: OSX" >&5 +printf '%s\n' "OSX" >&6; };; + *) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none" >&5 +printf '%s\n' "none" >&6; };; +esac + +#------------------------------------------------------------------------------ +# Find out all about time handling differences. +#------------------------------------------------------------------------------ + + + + ac_fn_c_check_header_compile "$LINENO" "sys/time.h" "ac_cv_header_sys_time_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_time_h" = xyes +then : + printf '%s\n' "#define HAVE_SYS_TIME_H 1" >>confdefs.h + +fi + + + + ac_fn_c_check_func "$LINENO" "gmtime_r" "ac_cv_func_gmtime_r" +if test "x$ac_cv_func_gmtime_r" = xyes +then : + printf '%s\n' "#define HAVE_GMTIME_R 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "localtime_r" "ac_cv_func_localtime_r" +if test "x$ac_cv_func_localtime_r" = xyes +then : + printf '%s\n' "#define HAVE_LOCALTIME_R 1" >>confdefs.h + +fi + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking tm_tzadj in struct tm" >&5 +printf %s "checking tm_tzadj in struct tm... " >&6; } +if test ${tcl_cv_member_tm_tzadj+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +struct tm tm; (void)tm.tm_tzadj; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_member_tm_tzadj=yes +else case e in #( + e) tcl_cv_member_tm_tzadj=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_member_tm_tzadj" >&5 +printf '%s\n' "$tcl_cv_member_tm_tzadj" >&6; } + if test $tcl_cv_member_tm_tzadj = yes ; then + +printf '%s\n' "#define HAVE_TM_TZADJ 1" >>confdefs.h + + fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking tm_gmtoff in struct tm" >&5 +printf %s "checking tm_gmtoff in struct tm... " >&6; } +if test ${tcl_cv_member_tm_gmtoff+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +struct tm tm; (void)tm.tm_gmtoff; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_member_tm_gmtoff=yes +else case e in #( + e) tcl_cv_member_tm_gmtoff=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_member_tm_gmtoff" >&5 +printf '%s\n' "$tcl_cv_member_tm_gmtoff" >&6; } + if test $tcl_cv_member_tm_gmtoff = yes ; then + +printf '%s\n' "#define HAVE_TM_GMTOFF 1" >>confdefs.h + + fi + + # + # Its important to include time.h in this check, as some systems + # (like convex) have timezone functions, etc. + # + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking long timezone variable" >&5 +printf %s "checking long timezone variable... " >&6; } +if test ${tcl_cv_timezone_long+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +int +main (void) +{ +extern long timezone; + timezone += 1; + exit (0); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_timezone_long=yes +else case e in #( + e) tcl_cv_timezone_long=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_timezone_long" >&5 +printf '%s\n' "$tcl_cv_timezone_long" >&6; } + if test $tcl_cv_timezone_long = yes ; then + +printf '%s\n' "#define HAVE_TIMEZONE_VAR 1" >>confdefs.h + + else + # + # On some systems (eg IRIX 6.2), timezone is a time_t and not a long. + # + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking time_t timezone variable" >&5 +printf %s "checking time_t timezone variable... " >&6; } +if test ${tcl_cv_timezone_time+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +int +main (void) +{ +extern time_t timezone; + timezone += 1; + exit (0); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_timezone_time=yes +else case e in #( + e) tcl_cv_timezone_time=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_timezone_time" >&5 +printf '%s\n' "$tcl_cv_timezone_time" >&6; } + if test $tcl_cv_timezone_time = yes ; then + +printf '%s\n' "#define HAVE_TIMEZONE_VAR 1" >>confdefs.h + + fi + fi + + +#-------------------------------------------------------------------- +# Some systems (e.g., IRIX 4.0.5) lack some fields in struct stat. But +# we might be able to use fstatfs instead. Some systems (OpenBSD?) also +# lack blkcnt_t. +#-------------------------------------------------------------------- + +if test "$ac_cv_cygwin" != "yes"; then + ac_fn_c_check_member "$LINENO" "struct stat" "st_blocks" "ac_cv_member_struct_stat_st_blocks" "$ac_includes_default" +if test "x$ac_cv_member_struct_stat_st_blocks" = xyes +then : + +printf '%s\n' "#define HAVE_STRUCT_STAT_ST_BLOCKS 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct stat" "st_blksize" "ac_cv_member_struct_stat_st_blksize" "$ac_includes_default" +if test "x$ac_cv_member_struct_stat_st_blksize" = xyes +then : + +printf '%s\n' "#define HAVE_STRUCT_STAT_ST_BLKSIZE 1" >>confdefs.h + + +fi +ac_fn_c_check_member "$LINENO" "struct stat" "st_rdev" "ac_cv_member_struct_stat_st_rdev" "$ac_includes_default" +if test "x$ac_cv_member_struct_stat_st_rdev" = xyes +then : + +printf '%s\n' "#define HAVE_STRUCT_STAT_ST_RDEV 1" >>confdefs.h + + +fi + +fi +ac_fn_c_check_type "$LINENO" "blkcnt_t" "ac_cv_type_blkcnt_t" "$ac_includes_default" +if test "x$ac_cv_type_blkcnt_t" = xyes +then : + +printf '%s\n' "#define HAVE_BLKCNT_T 1" >>confdefs.h + + +fi + +ac_fn_c_check_func "$LINENO" "fstatfs" "ac_cv_func_fstatfs" +if test "x$ac_cv_func_fstatfs" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define NO_FSTATFS 1" >>confdefs.h + ;; +esac +fi + + +#-------------------------------------------------------------------- +# Check for various typedefs and provide substitutes if +# they don't exist. +#-------------------------------------------------------------------- + +ac_fn_c_check_type "$LINENO" "mode_t" "ac_cv_type_mode_t" "$ac_includes_default" +if test "x$ac_cv_type_mode_t" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define mode_t int" >>confdefs.h + ;; +esac +fi + + + ac_fn_c_check_type "$LINENO" "pid_t" "ac_cv_type_pid_t" "$ac_includes_default +" +if test "x$ac_cv_type_pid_t" = xyes +then : + +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #if defined _WIN64 && !defined __CYGWIN__ + LLP64 + #endif + +int +main (void) +{ + + ; + return 0; +} + +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_pid_type='int' +else case e in #( + e) ac_pid_type='__int64' ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + +printf '%s\n' "#define pid_t $ac_pid_type" >>confdefs.h + + ;; +esac +fi + + +ac_fn_c_check_type "$LINENO" "size_t" "ac_cv_type_size_t" "$ac_includes_default" +if test "x$ac_cv_type_size_t" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define size_t unsigned int" >>confdefs.h + ;; +esac +fi + +ac_fn_c_check_type "$LINENO" "uid_t" "ac_cv_type_uid_t" "$ac_includes_default" +if test "x$ac_cv_type_uid_t" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define uid_t int" >>confdefs.h + ;; +esac +fi + +ac_fn_c_check_type "$LINENO" "gid_t" "ac_cv_type_gid_t" "$ac_includes_default" +if test "x$ac_cv_type_gid_t" = xyes +then : + +else case e in #( + e) +printf '%s\n' "#define gid_t int" >>confdefs.h + ;; +esac +fi + + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for socklen_t" >&5 +printf %s "checking for socklen_t... " >&6; } +if test ${tcl_cv_type_socklen_t+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + +int +main (void) +{ + + socklen_t foo; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_type_socklen_t=yes +else case e in #( + e) tcl_cv_type_socklen_t=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_type_socklen_t" >&5 +printf '%s\n' "$tcl_cv_type_socklen_t" >&6; } +if test $tcl_cv_type_socklen_t = no; then + +printf '%s\n' "#define socklen_t int" >>confdefs.h + +fi + +ac_fn_c_check_type "$LINENO" "intptr_t" "ac_cv_type_intptr_t" " +#include + +" +if test "x$ac_cv_type_intptr_t" = xyes +then : + +printf '%s\n' "#define HAVE_INTPTR_T 1" >>confdefs.h + + +fi +ac_fn_c_check_type "$LINENO" "uintptr_t" "ac_cv_type_uintptr_t" " +#include + +" +if test "x$ac_cv_type_uintptr_t" = xyes +then : + +printf '%s\n' "#define HAVE_UINTPTR_T 1" >>confdefs.h + + +fi + + +#-------------------------------------------------------------------- +# The check below checks whether defines the type +# "union wait" correctly. It's needed because of weirdness in +# HP-UX where "union wait" is defined in both the BSD and SYS-V +# environments. Checking the usability of WIFEXITED seems to do +# the trick. +#-------------------------------------------------------------------- + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking union wait" >&5 +printf %s "checking union wait... " >&6; } +if test ${tcl_cv_union_wait+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +int +main (void) +{ + +union wait x; +WIFEXITED(x); /* Generates compiler error if WIFEXITED + * uses an int. */ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_union_wait=yes +else case e in #( + e) tcl_cv_union_wait=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_union_wait" >&5 +printf '%s\n' "$tcl_cv_union_wait" >&6; } +if test $tcl_cv_union_wait = no; then + +printf '%s\n' "#define NO_UNION_WAIT 1" >>confdefs.h + +fi + +#-------------------------------------------------------------------- +# Check whether there is an strncasecmp function on this system. +# This is a bit tricky because under SCO it's in -lsocket and +# under Sequent Dynix it's in -linet. +#-------------------------------------------------------------------- + +ac_fn_c_check_func "$LINENO" "strncasecmp" "ac_cv_func_strncasecmp" +if test "x$ac_cv_func_strncasecmp" = xyes +then : + tcl_ok=1 +else case e in #( + e) tcl_ok=0 ;; +esac +fi + +if test "$tcl_ok" = 0; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for strncasecmp in -lsocket" >&5 +printf %s "checking for strncasecmp in -lsocket... " >&6; } +if test ${ac_cv_lib_socket_strncasecmp+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-lsocket $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char strncasecmp (void); +int +main (void) +{ +return strncasecmp (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_socket_strncasecmp=yes +else case e in #( + e) ac_cv_lib_socket_strncasecmp=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_strncasecmp" >&5 +printf '%s\n' "$ac_cv_lib_socket_strncasecmp" >&6; } +if test "x$ac_cv_lib_socket_strncasecmp" = xyes +then : + tcl_ok=1 +else case e in #( + e) tcl_ok=0 ;; +esac +fi + +fi +if test "$tcl_ok" = 0; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for strncasecmp in -linet" >&5 +printf %s "checking for strncasecmp in -linet... " >&6; } +if test ${ac_cv_lib_inet_strncasecmp+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_check_lib_save_LIBS=$LIBS +LIBS="-linet $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. + The 'extern "C"' is for builds by C++ compilers; + although this is not generally supported in C code supporting it here + has little cost and some practical benefit (sr 110532). */ +#ifdef __cplusplus +extern "C" +#endif +char strncasecmp (void); +int +main (void) +{ +return strncasecmp (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_inet_strncasecmp=yes +else case e in #( + e) ac_cv_lib_inet_strncasecmp=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_inet_strncasecmp" >&5 +printf '%s\n' "$ac_cv_lib_inet_strncasecmp" >&6; } +if test "x$ac_cv_lib_inet_strncasecmp" = xyes +then : + tcl_ok=1 +else case e in #( + e) tcl_ok=0 ;; +esac +fi + +fi +if test "$tcl_ok" = 0; then + case " $LIBOBJS " in + *" strncasecmp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS strncasecmp.$ac_objext" + ;; +esac + + USE_COMPAT=1 +fi + +#-------------------------------------------------------------------- +# The code below deals with several issues related to gettimeofday: +# 1. Some systems don't provide a gettimeofday function at all +# (set NO_GETTOD if this is the case). +# 2. See if gettimeofday is declared in the header file. +# if not, set the GETTOD_NOT_DECLARED flag so that tclPort.h can +# declare it. +#-------------------------------------------------------------------- + +ac_fn_c_check_func "$LINENO" "gettimeofday" "ac_cv_func_gettimeofday" +if test "x$ac_cv_func_gettimeofday" = xyes +then : + +else case e in #( + e) + +printf '%s\n' "#define NO_GETTOD 1" >>confdefs.h + + ;; +esac +fi + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for gettimeofday declaration" >&5 +printf %s "checking for gettimeofday declaration... " >&6; } +if test ${tcl_cv_grep_gettimeofday+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP_TRADITIONAL "gettimeofday" >/dev/null 2>&1 +then : + tcl_cv_grep_gettimeofday=present +else case e in #( + e) tcl_cv_grep_gettimeofday=missing ;; +esac +fi +rm -rf conftest* + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_grep_gettimeofday" >&5 +printf '%s\n' "$tcl_cv_grep_gettimeofday" >&6; } +if test $tcl_cv_grep_gettimeofday = missing ; then + +printf '%s\n' "#define GETTOD_NOT_DECLARED 1" >>confdefs.h + +fi + +#-------------------------------------------------------------------- +# The following code checks to see whether it is possible to get +# signed chars on this platform. This is needed in order to +# properly generate sign-extended ints from character values. +#-------------------------------------------------------------------- + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether char is unsigned" >&5 +printf %s "checking whether char is unsigned... " >&6; } +if test ${ac_cv_c_char_unsigned+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_includes_default +int +main (void) +{ +static int test_array [1 - 2 * !(((char) -1) < 0)]; +test_array [0] = 0; +return test_array [0]; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_c_char_unsigned=no +else case e in #( + e) ac_cv_c_char_unsigned=yes ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_char_unsigned" >&5 +printf '%s\n' "$ac_cv_c_char_unsigned" >&6; } +if test $ac_cv_c_char_unsigned = yes; then + printf '%s\n' "#define __CHAR_UNSIGNED__ 1" >>confdefs.h + +fi + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking signed char declarations" >&5 +printf %s "checking signed char declarations... " >&6; } +if test ${tcl_cv_char_signed+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + signed char *p; + p = 0; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_char_signed=yes +else case e in #( + e) tcl_cv_char_signed=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_char_signed" >&5 +printf '%s\n' "$tcl_cv_char_signed" >&6; } +if test $tcl_cv_char_signed = yes; then + +printf '%s\n' "#define HAVE_SIGNED_CHAR 1" >>confdefs.h + +fi + +#-------------------------------------------------------------------- +# Does putenv() copy or not? We need to know to avoid memory leaks. +#-------------------------------------------------------------------- + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for a putenv() that copies the buffer" >&5 +printf %s "checking for a putenv() that copies the buffer... " >&6; } +if test ${tcl_cv_putenv_copy+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + if test "$cross_compiling" = yes +then : + tcl_cv_putenv_copy=no +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + #define OURVAR "havecopy=yes" + int main (int argc, char *argv[]) + { + char *foo, *bar; + foo = (char *)strdup(OURVAR); + putenv(foo); + strcpy((char *)(strchr(foo, '=') + 1), "no"); + bar = getenv("havecopy"); + if (!strcmp(bar, "no")) { + /* doesnt copy */ + return 0; + } else { + /* does copy */ + return 1; + } + } + +_ACEOF +if ac_fn_c_try_run "$LINENO" +then : + tcl_cv_putenv_copy=no +else case e in #( + e) tcl_cv_putenv_copy=yes ;; +esac +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_putenv_copy" >&5 +printf '%s\n' "$tcl_cv_putenv_copy" >&6; } +if test $tcl_cv_putenv_copy = yes; then + +printf '%s\n' "#define HAVE_PUTENV_THAT_COPIES 1" >>confdefs.h + +fi + +#-------------------------------------------------------------------- +# Check for support of nl_langinfo function +#-------------------------------------------------------------------- + + + # Check whether --enable-langinfo was given. +if test ${enable_langinfo+y} +then : + enableval=$enable_langinfo; langinfo_ok=$enableval +else case e in #( + e) langinfo_ok=yes ;; +esac +fi + + + HAVE_LANGINFO=0 + if test "$langinfo_ok" = "yes"; then + ac_fn_c_check_header_compile "$LINENO" "langinfo.h" "ac_cv_header_langinfo_h" "$ac_includes_default" +if test "x$ac_cv_header_langinfo_h" = xyes +then : + langinfo_ok=yes +else case e in #( + e) langinfo_ok=no ;; +esac +fi + + fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to use nl_langinfo" >&5 +printf %s "checking whether to use nl_langinfo... " >&6; } + if test "$langinfo_ok" = "yes"; then + if test ${tcl_cv_langinfo_h+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +nl_langinfo(CODESET); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_langinfo_h=yes +else case e in #( + e) tcl_cv_langinfo_h=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_langinfo_h" >&5 +printf '%s\n' "$tcl_cv_langinfo_h" >&6; } + if test $tcl_cv_langinfo_h = yes; then + +printf '%s\n' "#define HAVE_LANGINFO 1" >>confdefs.h + + fi + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $langinfo_ok" >&5 +printf '%s\n' "$langinfo_ok" >&6; } + fi + + +#-------------------------------------------------------------------- +# Check for support of cfmakeraw, chflags and mkstemps functions +#-------------------------------------------------------------------- + +ac_fn_c_check_func "$LINENO" "cfmakeraw" "ac_cv_func_cfmakeraw" +if test "x$ac_cv_func_cfmakeraw" = xyes +then : + printf '%s\n' "#define HAVE_CFMAKERAW 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "chflags" "ac_cv_func_chflags" +if test "x$ac_cv_func_chflags" = xyes +then : + printf '%s\n' "#define HAVE_CHFLAGS 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "mkstemps" "ac_cv_func_mkstemps" +if test "x$ac_cv_func_mkstemps" = xyes +then : + printf '%s\n' "#define HAVE_MKSTEMPS 1" >>confdefs.h + +fi + + +#-------------------------------------------------------------------- +# Darwin specific API checks and defines +#-------------------------------------------------------------------- + +if test "`uname -s`" = "Darwin" ; then + ac_fn_c_check_func "$LINENO" "getattrlist" "ac_cv_func_getattrlist" +if test "x$ac_cv_func_getattrlist" = xyes +then : + printf '%s\n' "#define HAVE_GETATTRLIST 1" >>confdefs.h + +fi + + ac_fn_c_check_header_compile "$LINENO" "copyfile.h" "ac_cv_header_copyfile_h" "$ac_includes_default" +if test "x$ac_cv_header_copyfile_h" = xyes +then : + printf '%s\n' "#define HAVE_COPYFILE_H 1" >>confdefs.h + +fi + + ac_fn_c_check_func "$LINENO" "copyfile" "ac_cv_func_copyfile" +if test "x$ac_cv_func_copyfile" = xyes +then : + printf '%s\n' "#define HAVE_COPYFILE 1" >>confdefs.h + +fi + + if test $tcl_corefoundation = yes; then + ac_fn_c_check_header_compile "$LINENO" "libkern/OSAtomic.h" "ac_cv_header_libkern_OSAtomic_h" "$ac_includes_default" +if test "x$ac_cv_header_libkern_OSAtomic_h" = xyes +then : + printf '%s\n' "#define HAVE_LIBKERN_OSATOMIC_H 1" >>confdefs.h + +fi + + ac_fn_c_check_func "$LINENO" "OSSpinLockLock" "ac_cv_func_OSSpinLockLock" +if test "x$ac_cv_func_OSSpinLockLock" = xyes +then : + printf '%s\n' "#define HAVE_OSSPINLOCKLOCK 1" >>confdefs.h + +fi + + fi + +printf '%s\n' "#define TCL_LOAD_FROM_MEMORY 1" >>confdefs.h + + +printf '%s\n' "#define TCL_WIDE_CLICKS 1" >>confdefs.h + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if weak import is available" >&5 +printf %s "checking if weak import is available... " >&6; } +if test ${tcl_cv_cc_weak_import+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + int rand(void) __attribute__((weak_import)); + +int +main (void) +{ +rand(); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_cc_weak_import=yes +else case e in #( + e) tcl_cv_cc_weak_import=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_weak_import" >&5 +printf '%s\n' "$tcl_cv_cc_weak_import" >&6; } + if test $tcl_cv_cc_weak_import = yes; then + +printf '%s\n' "#define HAVE_WEAK_IMPORT 1" >>confdefs.h + + fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if Darwin SUSv3 extensions are available" >&5 +printf %s "checking if Darwin SUSv3 extensions are available... " >&6; } +if test ${tcl_cv_cc_darwin_c_source+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Werror" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #define _DARWIN_C_SOURCE 1 + #include + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_cc_darwin_c_source=yes +else case e in #( + e) tcl_cv_cc_darwin_c_source=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_darwin_c_source" >&5 +printf '%s\n' "$tcl_cv_cc_darwin_c_source" >&6; } + if test $tcl_cv_cc_darwin_c_source = yes; then + +printf '%s\n' "#define _DARWIN_C_SOURCE 1" >>confdefs.h + + fi + # Build .bundle dltest binaries in addition to .dylib + DLTEST_LD='${CC} -bundle -Wl,-w ${CFLAGS} ${LDFLAGS}' + DLTEST_SUFFIX=".bundle" +else + DLTEST_LD='${SHLIB_LD}' + DLTEST_SUFFIX="" +fi + +#-------------------------------------------------------------------- +# Check for support of fts functions (readdir replacement) +#-------------------------------------------------------------------- + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for fts" >&5 +printf %s "checking for fts... " >&6; } +if test ${tcl_cv_api_fts+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #include + #include + #include + +int +main (void) +{ + + char*const p[2] = {"/", NULL}; + FTS *f = fts_open(p, FTS_PHYSICAL|FTS_NOCHDIR|FTS_NOSTAT, NULL); + FTSENT *e = fts_read(f); fts_close(f); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_api_fts=yes +else case e in #( + e) tcl_cv_api_fts=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_api_fts" >&5 +printf '%s\n' "$tcl_cv_api_fts" >&6; } +if test $tcl_cv_api_fts = yes; then + +printf '%s\n' "#define HAVE_FTS 1" >>confdefs.h + +fi + +#-------------------------------------------------------------------- +# The statements below check for systems where POSIX-style non-blocking +# I/O (O_NONBLOCK) doesn't work or is unimplemented. On these systems +# (mostly older ones), use the old BSD-style FIONBIO approach instead. +#-------------------------------------------------------------------- + + + ac_fn_c_check_header_compile "$LINENO" "sys/ioctl.h" "ac_cv_header_sys_ioctl_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_ioctl_h" = xyes +then : + printf '%s\n' "#define HAVE_SYS_IOCTL_H 1" >>confdefs.h + +fi + + ac_fn_c_check_header_compile "$LINENO" "sys/filio.h" "ac_cv_header_sys_filio_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_filio_h" = xyes +then : + printf '%s\n' "#define HAVE_SYS_FILIO_H 1" >>confdefs.h + +fi + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking system version" >&5 +printf %s "checking system version... " >&6; } +if test ${tcl_cv_sys_version+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + if test "${TEA_PLATFORM}" = "windows" ; then + tcl_cv_sys_version=windows + else + tcl_cv_sys_version=`uname -s`-`uname -r` + if test "$?" -ne 0 ; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: can't find uname command" >&5 +printf '%s\n' "$as_me: WARNING: can't find uname command" >&2;} + tcl_cv_sys_version=unknown + else + if test "`uname -s`" = "AIX" ; then + tcl_cv_sys_version=AIX-`uname -v`.`uname -r` + fi + if test "`uname -s`" = "NetBSD" -a -f /etc/debian_version ; then + tcl_cv_sys_version=NetBSD-Debian + fi + fi + fi + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_sys_version" >&5 +printf '%s\n' "$tcl_cv_sys_version" >&6; } + system=$tcl_cv_sys_version + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking FIONBIO vs. O_NONBLOCK for nonblocking I/O" >&5 +printf %s "checking FIONBIO vs. O_NONBLOCK for nonblocking I/O... " >&6; } + case $system in + OSF*) + +printf '%s\n' "#define USE_FIONBIO 1" >>confdefs.h + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: FIONBIO" >&5 +printf '%s\n' "FIONBIO" >&6; } + ;; + *) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: O_NONBLOCK" >&5 +printf '%s\n' "O_NONBLOCK" >&6; } + ;; + esac + + +#------------------------------------------------------------------------ + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to use dll unloading" >&5 +printf %s "checking whether to use dll unloading... " >&6; } +# Check whether --enable-dll-unloading was given. +if test ${enable_dll_unloading+y} +then : + enableval=$enable_dll_unloading; tcl_ok=$enableval +else case e in #( + e) tcl_ok=yes ;; +esac +fi + +if test $tcl_ok = yes; then + +printf '%s\n' "#define TCL_UNLOAD_DLLS 1" >>confdefs.h + +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_ok" >&5 +printf '%s\n' "$tcl_ok" >&6; } + +#------------------------------------------------------------------------ +# Check whether the timezone data is supplied by the OS or has +# to be installed by Tcl. The default is autodetection, but can +# be overridden on the configure command line either way. +#------------------------------------------------------------------------ + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for timezone data" >&5 +printf %s "checking for timezone data... " >&6; } + +# Check whether --with-tzdata was given. +if test ${with_tzdata+y} +then : + withval=$with_tzdata; tcl_ok=$withval +else case e in #( + e) tcl_ok=auto ;; +esac +fi + +# +# Any directories that get added here must also be added to the +# search path in ::tcl::clock::Initialize (library/clock.tcl). +# +case $tcl_ok in + no) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: supplied by OS vendor" >&5 +printf '%s\n' "supplied by OS vendor" >&6; } + ;; + yes) + # nothing to do here + ;; + auto*) + if test ${tcl_cv_dir_zoneinfo+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + for dir in /usr/share/zoneinfo \ + /usr/share/lib/zoneinfo \ + /usr/lib/zoneinfo + do + if test -f $dir/UTC -o -f $dir/GMT + then + tcl_cv_dir_zoneinfo="$dir" + break + fi + done ;; +esac +fi + + if test -n "$tcl_cv_dir_zoneinfo"; then + tcl_ok=no + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $dir" >&5 +printf '%s\n' "$dir" >&6; } + else + tcl_ok=yes + fi + ;; + *) + as_fn_error $? "invalid argument: $tcl_ok" "$LINENO" 5 + ;; +esac +if test $tcl_ok = yes +then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: supplied by Tcl" >&5 +printf '%s\n' "supplied by Tcl" >&6; } + INSTALL_TZDATA=install-tzdata +fi + +#-------------------------------------------------------------------- +# DTrace support +#-------------------------------------------------------------------- + +# Check whether --enable-dtrace was given. +if test ${enable_dtrace+y} +then : + enableval=$enable_dtrace; tcl_ok=$enableval +else case e in #( + e) tcl_ok=no ;; +esac +fi + +if test $tcl_ok = yes; then + ac_fn_c_check_header_compile "$LINENO" "sys/sdt.h" "ac_cv_header_sys_sdt_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_sdt_h" = xyes +then : + tcl_ok=yes +else case e in #( + e) tcl_ok=no ;; +esac +fi + +fi +if test $tcl_ok = yes; then + # Extract the first word of "dtrace", so it can be a program name with args. +set dummy dtrace; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_path_DTRACE+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) case $DTRACE in + [\\/]* | ?:[\\/]*) + ac_cv_path_DTRACE="$DTRACE" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_dummy="$PATH:/usr/sbin" +for as_dir in $as_dummy +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_path_DTRACE="$as_dir$ac_word$ac_exec_ext" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac ;; +esac +fi +DTRACE=$ac_cv_path_DTRACE +if test -n "$DTRACE"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $DTRACE" >&5 +printf '%s\n' "$DTRACE" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + + test -z "$ac_cv_path_DTRACE" && tcl_ok=no +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to enable DTrace support" >&5 +printf %s "checking whether to enable DTrace support... " >&6; } +MAKEFILE_SHELL='/bin/sh' +if test $tcl_ok = yes; then + +printf '%s\n' "#define USE_DTRACE 1" >>confdefs.h + + DTRACE_SRC="\${DTRACE_SRC}" + DTRACE_HDR="\${DTRACE_HDR}" + if test "`uname -s`" != "Darwin" ; then + DTRACE_OBJ="\${DTRACE_OBJ}" + if test "`uname -s`" = "SunOS" -a "$SHARED_BUILD" = "0" ; then + # Need to create an intermediate object file to ensure tclDTrace.o + # gets included when linking against the static tcl library. + STLIB_LD='stlib_ld () { /usr/ccs/bin/ld -r -o $${1%.a}.o "$${@:2}" && '"${STLIB_LD}"' $${1} $${1%.a}.o ; } && stlib_ld' + MAKEFILE_SHELL='/bin/bash' + # Force use of Sun ar and ranlib, the GNU versions choke on + # tclDTrace.o and the combined object file above. + AR='/usr/ccs/bin/ar' + RANLIB='/usr/ccs/bin/ranlib' + fi + fi +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_ok" >&5 +printf '%s\n' "$tcl_ok" >&6; } + +#-------------------------------------------------------------------- +# The check below checks whether the cpuid instruction is usable. +#-------------------------------------------------------------------- + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the cpuid instruction is usable" >&5 +printf %s "checking whether the cpuid instruction is usable... " >&6; } +if test ${tcl_cv_cpuid+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + int index,regsPtr[4]; + __asm__ __volatile__("mov %%ebx, %%edi \n\t" + "cpuid \n\t" + "mov %%ebx, %%esi \n\t" + "mov %%edi, %%ebx \n\t" + : "=a"(regsPtr[0]), "=S"(regsPtr[1]), "=c"(regsPtr[2]), "=d"(regsPtr[3]) + : "a"(index) : "edi"); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_cpuid=yes +else case e in #( + e) tcl_cv_cpuid=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cpuid" >&5 +printf '%s\n' "$tcl_cv_cpuid" >&6; } +if test $tcl_cv_cpuid = yes; then + +printf '%s\n' "#define HAVE_CPUID 1" >>confdefs.h + +fi + +#-------------------------------------------------------------------- +# The statements below define a collection of symbols related to +# building libtcl as a shared library instead of a static library. +#-------------------------------------------------------------------- + +TCL_UNSHARED_LIB_SUFFIX=${UNSHARED_LIB_SUFFIX} +TCL_SHARED_LIB_SUFFIX=${SHARED_LIB_SUFFIX} +if test "$ac_cv_cygwin" = "yes" -a "$SHARED_BUILD" != "0"; then +eval "TCL_LIB_FILE=cygtcl${LIB_SUFFIX}" +EXTRA_INSTALL_BINARIES='$(INSTALL_LIBRARY) $(patsubst cyg%.dll,lib%.dll.a,${LIB_FILE}) "$(LIB_INSTALL_DIR)"' +else +eval "TCL_LIB_FILE=libtcl${LIB_SUFFIX}" +fi + +# tclConfig.sh needs a version of the _LIB_SUFFIX that has been eval'ed +# since on some platforms TCL_LIB_FILE contains shell escapes. +# (See also: TCL_TRIM_DOTS). + +eval "TCL_LIB_FILE=${TCL_LIB_FILE}" + +test -z "$TCL_LIBRARY" && TCL_LIBRARY='$(prefix)/lib/tcl$(VERSION)' +PRIVATE_INCLUDE_DIR='$(includedir)' +HTML_DIR='$(DISTDIR)/html' + +# Note: in the following variable, it's important to use the absolute +# path name of the Tcl directory rather than "..": this is because +# AIX remembers this path and will attempt to use it at run-time to look +# up the Tcl library. + +if test "`uname -s`" = "Darwin" ; then + + if test "`uname -s`" = "Darwin" ; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to package libraries" >&5 +printf %s "checking how to package libraries... " >&6; } + # Check whether --enable-framework was given. +if test ${enable_framework+y} +then : + enableval=$enable_framework; enable_framework=$enableval +else case e in #( + e) enable_framework=no ;; +esac +fi + + if test $enable_framework = yes; then + if test $SHARED_BUILD = 0; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: Frameworks can only be built if --enable-shared is yes" >&5 +printf '%s\n' "$as_me: WARNING: Frameworks can only be built if --enable-shared is yes" >&2;} + enable_framework=no + fi + if test $tcl_corefoundation = no; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: Frameworks can only be used when CoreFoundation is available" >&5 +printf '%s\n' "$as_me: WARNING: Frameworks can only be used when CoreFoundation is available" >&2;} + enable_framework=no + fi + fi + if test $enable_framework = yes; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: framework" >&5 +printf '%s\n' "framework" >&6; } + FRAMEWORK_BUILD=1 + else + if test $SHARED_BUILD = 1; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: shared library" >&5 +printf '%s\n' "shared library" >&6; } + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: static library" >&5 +printf '%s\n' "static library" >&6; } + fi + FRAMEWORK_BUILD=0 + fi + fi + + TCL_SHLIB_LD_EXTRAS="-compatibility_version ${TCL_VERSION} -current_version ${TCL_VERSION}`echo ${TCL_PATCH_LEVEL} | awk '{match($0, "\\\.[0-9]+"); print substr($0,RSTART,RLENGTH)}'`" + TCL_SHLIB_LD_EXTRAS="${TCL_SHLIB_LD_EXTRAS}"' -install_name "${DYLIB_INSTALL_DIR}"/${TCL_LIB_FILE}' + echo "$LDFLAGS " | grep -q -- '-prebind ' && TCL_SHLIB_LD_EXTRAS="${TCL_SHLIB_LD_EXTRAS}"' -seg1addr 0xA000000' + TCL_SHLIB_LD_EXTRAS="${TCL_SHLIB_LD_EXTRAS}"' -sectcreate __TEXT __info_plist Tcl-Info.plist' + EXTRA_TCLSH_LIBS='-sectcreate __TEXT __info_plist Tclsh-Info.plist' + ac_config_files="$ac_config_files Tcl-Info.plist:../macosx/Tcl-Info.plist.in Tclsh-Info.plist:../macosx/Tclsh-Info.plist.in" + + TCL_YEAR="`date +%Y`" +fi + +if test "$FRAMEWORK_BUILD" = "1" ; then + +printf '%s\n' "#define TCL_FRAMEWORK 1" >>confdefs.h + + # Construct a fake local framework structure to make linking with + # '-framework Tcl' and running of tcltest work + ac_config_commands="$ac_config_commands Tcl.framework" + + LD_LIBRARY_PATH_VAR="DYLD_FRAMEWORK_PATH" + # default install directory for bundled packages + if test "${libdir}" = '${exec_prefix}/lib' -o "`basename ${libdir}`" = 'Frameworks'; then + PACKAGE_DIR="/Library/Tcl" + else + PACKAGE_DIR="$libdir" + fi + if test "${libdir}" = '${exec_prefix}/lib'; then + # override libdir default + libdir="/Library/Frameworks" + fi + TCL_LIB_FILE="Tcl" + TCL_LIB_FLAG="-framework Tcl" + TCL_BUILD_LIB_SPEC="-F`pwd | sed -e 's/ /\\\\ /g'` -framework Tcl" + TCL_LIB_SPEC="-F${libdir} -framework Tcl" + libdir="${libdir}/Tcl.framework/Versions/\${VERSION}" + TCL_LIBRARY="${libdir}/Resources/Scripts" + includedir="${libdir}/Headers" + PRIVATE_INCLUDE_DIR="${libdir}/PrivateHeaders" + HTML_DIR="${libdir}/Resources/Documentation/Reference/Tcl" + EXTRA_INSTALL="install-private-headers html-tcl" + EXTRA_BUILD_HTML='@ln -fs contents.htm "$(HTML_INSTALL_DIR)/TclTOC.html"' + EXTRA_INSTALL_BINARIES='@echo "Installing Info.plist to $(LIB_INSTALL_DIR)/Resources/" && $(INSTALL_DATA_DIR) "$(LIB_INSTALL_DIR)/Resources" && $(INSTALL_DATA) Tcl-Info.plist "$(LIB_INSTALL_DIR)/Resources/Info.plist"' + EXTRA_INSTALL_BINARIES="$EXTRA_INSTALL_BINARIES"' && echo "Installing license.terms to $(LIB_INSTALL_DIR)/Resources/" && $(INSTALL_DATA) "$(TOP_DIR)/license.terms" "$(LIB_INSTALL_DIR)/Resources"' + EXTRA_INSTALL_BINARIES="$EXTRA_INSTALL_BINARIES"' && echo "Finalizing Tcl.framework" && rm -f "$(LIB_INSTALL_DIR)/../Current" && ln -s "$(VERSION)" "$(LIB_INSTALL_DIR)/../Current" && for f in "$(LIB_FILE)" tclConfig.sh Resources Headers PrivateHeaders; do rm -f "$(LIB_INSTALL_DIR)/../../$$f" && ln -s "Versions/Current/$$f" "$(LIB_INSTALL_DIR)/../.."; done && f="$(STUB_LIB_FILE)" && rm -f "$(LIB_INSTALL_DIR)/../../$$f" && ln -s "Versions/$(VERSION)/$$f" "$(LIB_INSTALL_DIR)/../.."' + # Don't use AC_DEFINE for the following as the framework version define + # needs to go into the Makefile even when using autoheader, so that we + # can pick up a potential make override of VERSION. Also, don't put this + # into CFLAGS as it should not go into tclConfig.sh + EXTRA_CC_SWITCHES='-DTCL_FRAMEWORK_VERSION=\"$(VERSION)\"' +else + # libdir must be a fully qualified path and not ${exec_prefix}/lib + eval libdir="$libdir" + # default install directory for bundled packages + PACKAGE_DIR="$libdir" + if test "${TCL_LIB_VERSIONS_OK}" = "ok"; then + TCL_LIB_FLAG="-ltcl${TCL_VERSION}" + else + TCL_LIB_FLAG="-ltcl`echo ${TCL_VERSION} | tr -d .`" + fi + TCL_BUILD_LIB_SPEC="-L`pwd | sed -e 's/ /\\\\ /g'` ${TCL_LIB_FLAG}" + TCL_LIB_SPEC="-L${libdir} ${TCL_LIB_FLAG}" +fi +VERSION='${VERSION}' +eval "CFG_TCL_SHARED_LIB_SUFFIX=${TCL_SHARED_LIB_SUFFIX}" +eval "CFG_TCL_UNSHARED_LIB_SUFFIX=${TCL_UNSHARED_LIB_SUFFIX}" +VERSION=${TCL_VERSION} + +#-------------------------------------------------------------------- +# Zipfs support - Tip 430 +#-------------------------------------------------------------------- +# Check whether --enable-zipfs was given. +if test ${enable_zipfs+y} +then : + enableval=$enable_zipfs; tcl_ok=$enableval +else case e in #( + e) tcl_ok=yes ;; +esac +fi + +if test "$tcl_ok" = "yes" -a "x$enable_framework" != "xyes"; then + # + # Find a native compiler + # + # Put a plausible default for CC_FOR_BUILD in Makefile. + if test -z "$CC_FOR_BUILD"; then + if test "x$cross_compiling" = "xno"; then + CC_FOR_BUILD='$(CC)' + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for gcc" >&5 +printf %s "checking for gcc... " >&6; } + if test ${ac_cv_path_cc+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/gcc 2> /dev/null` \ + `ls -r $dir/gcc 2> /dev/null` ; do + if test x"$ac_cv_path_cc" = x ; then + if test -f "$j" ; then + ac_cv_path_cc=$j + break + fi + fi + done + done + ;; +esac +fi + + fi + fi + + # Also set EXEEXT_FOR_BUILD. + if test "x$cross_compiling" = "xno"; then + EXEEXT_FOR_BUILD='$(EXEEXT)' + OBJEXT_FOR_BUILD='$(OBJEXT)' + else + OBJEXT_FOR_BUILD='.no' + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for build system executable suffix" >&5 +printf %s "checking for build system executable suffix... " >&6; } +if test ${bfd_cv_build_exeext+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) rm -f conftest* + echo 'int main () { return 0; }' > conftest.c + bfd_cv_build_exeext= + ${CC_FOR_BUILD} -o conftest conftest.c 1>&5 2>&5 + for file in conftest.*; do + case $file in + *.c | *.o | *.obj | *.ilk | *.pdb) ;; + *) bfd_cv_build_exeext=`echo $file | sed -e s/conftest//` ;; + esac + done + rm -f conftest* + test x"${bfd_cv_build_exeext}" = x && bfd_cv_build_exeext=no ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $bfd_cv_build_exeext" >&5 +printf '%s\n' "$bfd_cv_build_exeext" >&6; } + EXEEXT_FOR_BUILD="" + test x"${bfd_cv_build_exeext}" != xno && EXEEXT_FOR_BUILD=${bfd_cv_build_exeext} + fi + + # + # Find a native zip implementation + # + + MACHER_PROG="" + ZIP_PROG="" + ZIP_PROG_OPTIONS="" + ZIP_PROG_VFSSEARCH="" + ZIP_INSTALL_OBJS="" + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for macher" >&5 +printf %s "checking for macher... " >&6; } + if test ${ac_cv_path_macher+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/macher 2> /dev/null` \ + `ls -r $dir/macher 2> /dev/null` ; do + if test x"$ac_cv_path_macher" = x ; then + if test -f "$j" ; then + ac_cv_path_macher=$j + break + fi + fi + done + done + ;; +esac +fi + + if test -f "$ac_cv_path_macher" ; then + MACHER_PROG="$ac_cv_path_macher" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $MACHER_PROG" >&5 +printf '%s\n' "$MACHER_PROG" >&6; } + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: Macher not found" >&5 +printf '%s\n' "Macher not found" >&6; } + fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for zip" >&5 +printf %s "checking for zip... " >&6; } + if test ${ac_cv_path_zip+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/zip 2> /dev/null` \ + `ls -r $dir/zip 2> /dev/null` ; do + if test x"$ac_cv_path_zip" = x ; then + if test -f "$j" ; then + ac_cv_path_zip=$j + break + fi + fi + done + done + ;; +esac +fi + + if test -f "$ac_cv_path_zip" ; then + ZIP_PROG="$ac_cv_path_zip" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ZIP_PROG" >&5 +printf '%s\n' "$ZIP_PROG" >&6; } + ZIP_PROG_OPTIONS="-rq" + ZIP_PROG_VFSSEARCH="*" + # Use standard arguments for zip + else + # It is not an error if an installed version of Zip can't be located. + # We can use the locally distributed minizip instead + ZIP_PROG="./minizip${EXEEXT_FOR_BUILD}" + ZIP_PROG_OPTIONS="-o -r" + ZIP_PROG_VFSSEARCH="*" + ZIP_INSTALL_OBJS="minizip${EXEEXT_FOR_BUILD}" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: No zip found on PATH. Building minizip" >&5 +printf '%s\n' "No zip found on PATH. Building minizip" >&6; } + fi + + + + + + + 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 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for building with zipfs" >&5 +printf %s "checking for building with zipfs... " >&6; } +if test "${ZIPFS_BUILD}" = 1; then + if test "${SHARED_BUILD}" = 0; then + ZIPFS_BUILD=2; + +printf '%s\n' "#define ZIPFS_BUILD 2" >>confdefs.h + + else + +printf '%s\n' "#define ZIPFS_BUILD 1" >>confdefs.h +\ + fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } +else +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +INSTALL_LIBRARIES=install-libraries +INSTALL_MSGS=install-msgs +fi + +# Point to tcl script library if we are not embedding it. +if test "${ZIPFS_BUILD}" = 0; then +TCL_BUILDTIME_LIBRARY=${TCL_SRC_DIR}/library +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 "$FRAMEWORK_BUILD" = "1" ; then + test -z "$TCL_PACKAGE_PATH" && \ + TCL_PACKAGE_PATH="~/Library/Tcl:/Library/Tcl:~/Library/Frameworks:/Library/Frameworks" + test -z "$TCL_MODULE_PATH" && \ + TCL_MODULE_PATH="~/Library/Tcl:/Library/Tcl" +elif test "$prefix/lib" != "$libdir"; then + test -z "$TCL_PACKAGE_PATH" && TCL_PACKAGE_PATH="${libdir}:${prefix}/lib" +else + test -z "$TCL_PACKAGE_PATH" && TCL_PACKAGE_PATH="${prefix}/lib" +fi + +#-------------------------------------------------------------------- +# The statements below define various symbols relating to Tcl +# stub support. +#-------------------------------------------------------------------- + +# Replace ${VERSION} with contents of ${TCL_VERSION} +# double-eval to account for TCL_TRIM_DOTS. +# +eval "TCL_STUB_LIB_FILE=libtclstub.a" +eval "TCL_STUB_LIB_FILE=\"${TCL_STUB_LIB_FILE}\"" +eval "TCL_STUB_LIB_DIR=\"${libdir}\"" + +TCL_STUB_LIB_FLAG="-ltclstub" + +TCL_BUILD_STUB_LIB_SPEC="-L`pwd | sed -e 's/ /\\\\ /g'` ${TCL_STUB_LIB_FLAG}" +TCL_STUB_LIB_SPEC="-L${TCL_STUB_LIB_DIR} ${TCL_STUB_LIB_FLAG}" +TCL_BUILD_STUB_LIB_PATH="`pwd`/${TCL_STUB_LIB_FILE}" +TCL_STUB_LIB_PATH="${TCL_STUB_LIB_DIR}/${TCL_STUB_LIB_FILE}" + +# Install time header dir can be set via --includedir +eval "TCL_INCLUDE_SPEC=\"-I${includedir}\"" + +#------------------------------------------------------------------------ +# tclConfig.sh refers to this by a different name +#------------------------------------------------------------------------ + +TCL_SHARED_BUILD=${SHARED_BUILD} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ac_config_files="$ac_config_files Makefile:../unix/Makefile.in dltest/Makefile:../unix/dltest/Makefile.in tclConfig.sh:../unix/tclConfig.sh.in tcl.pc:../unix/tcl.pc.in" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# 'ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* 'ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +ac_cache_dump | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +printf '%s\n' "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +printf '%s\n' "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +DEFS=-DHAVE_CONFIG_H + + +DEFS="-DHAVE_TCL_CONFIG_H -imacros tclConfig.h" +CFLAGS="${CFLAGS} ${CPPFLAGS}"; CPPFLAGS="" + +: "${CONFIG_STATUS=./config.status}" +case $CONFIG_STATUS in #( + -*) : + CONFIG_STATUS=./$CONFIG_STATUS ;; #( + */*) : + ;; #( + *) : + CONFIG_STATUS=./$CONFIG_STATUS ;; +esac + +ac_write_fail=0 +ac_clean_CONFIG_STATUS='"$CONFIG_STATUS"' +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +printf '%s\n' "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >"$CONFIG_STATUS" <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>"$CONFIG_STATUS" <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # contradicts POSIX and common usage. Disable this. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else case e in #( + e) case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac ;; +esac +fi + + + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. +as_nl=' +' +export as_nl +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi + +# The user is always right. +if ${PATH_SEPARATOR+false} :; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as 'sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + printf '%s\n' "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else case e in #( + e) as_fn_append () + { + eval $1=\$$1\$2 + } ;; +esac +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else case e in #( + e) as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } ;; +esac +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated + +# Sed expression to map a string onto a valid variable name. +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated + + +exec 6>&1 +## ------------------------------------- ## +## Main body of "$CONFIG_STATUS" script. ## +## ------------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x "$CONFIG_STATUS" || ac_write_fail=1 + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by tcl $as_me 9.0, which was +generated by GNU Autoconf 2.73. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + +case $ac_config_headers in *" +"*) set x $ac_config_headers; shift; ac_config_headers=$*;; +esac + + +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_headers="$ac_config_headers" +config_commands="$ac_config_commands" + +_ACEOF + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +'$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + --header=FILE[:TEMPLATE] + instantiate the configuration header FILE + +Configuration files: +$config_files + +Configuration headers: +$config_headers + +Configuration commands: +$config_commands + +Report bugs to the package provider." + +_ACEOF +ac_cs_config=`printf '%s\n' "$ac_configure_args" | sed "$ac_safe_unquote"` +ac_cs_config_escaped=`printf '%s\n' "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +ac_cs_config='$ac_cs_config_escaped' +ac_cs_version="\\ +tcl config.status 9.0 +configured by $0, generated by GNU Autoconf 2.73, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2026 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || { + awk '' >"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + printf '%s\n' "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + printf '%s\n' "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`printf '%s\n' "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --header | --heade | --head | --hea ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`printf '%s\n' "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append CONFIG_HEADERS " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h) + # Conflict between --help and --header + as_fn_error $? "ambiguous option: '$1' +Try '$0 --help' for more information.";; + --help | --hel | -h ) + printf '%s\n' "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: '$1' +Try '$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \printf '%s\n' "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + printf '%s\n' "$ac_log" +} >&5 + +_ACEOF +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +# +# INIT-COMMANDS +# +VERSION=${TCL_VERSION} + +_ACEOF + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "tclConfig.h") CONFIG_HEADERS="$CONFIG_HEADERS tclConfig.h:../unix/tclConfig.h.in" ;; + "Tcl-Info.plist") CONFIG_FILES="$CONFIG_FILES Tcl-Info.plist:../macosx/Tcl-Info.plist.in" ;; + "Tclsh-Info.plist") CONFIG_FILES="$CONFIG_FILES Tclsh-Info.plist:../macosx/Tclsh-Info.plist.in" ;; + "Tcl.framework") CONFIG_COMMANDS="$CONFIG_COMMANDS Tcl.framework" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile:../unix/Makefile.in" ;; + "dltest/Makefile") CONFIG_FILES="$CONFIG_FILES dltest/Makefile:../unix/dltest/Makefile.in" ;; + "tclConfig.sh") CONFIG_FILES="$CONFIG_FILES tclConfig.sh:../unix/tclConfig.sh.in" ;; + "tcl.pc") CONFIG_FILES="$CONFIG_FILES tcl.pc:../unix/tcl.pc.in" ;; + + *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files + test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers + test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to '$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with './config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | sed -n '$='` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | sed -n '$='` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >"$CONFIG_STATUS" || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + +# Set up the scripts for CONFIG_HEADERS section. +# No need to generate them if there are no CONFIG_HEADERS. +# This happens for instance with './config.status Makefile'. +if test -n "$CONFIG_HEADERS"; then +cat >"$ac_tmp/defines.awk" <<\_ACAWK || +BEGIN { +_ACEOF + +# Transform confdefs.h into an awk script 'defines.awk', embedded as +# here-document in config.status, that substitutes the proper values into +# config.h.in to produce config.h. + +# Create a delimiter string that does not exist in confdefs.h, to ease +# handling of long lines. +ac_delim='%!_!# ' +for ac_last_try in false false :; do + ac_tt=`sed -n "/$ac_delim/p" confdefs.h` + if test -z "$ac_tt"; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done + +# For the awk script, D is an array of macro values keyed by name, +# likewise P contains macro parameters if any. Preserve backslash +# newline sequences. + +ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* +sed -n ' +s/.\{148\}/&'"$ac_delim"'/g +t rset +:rset +s/^[ ]*#[ ]*define[ ][ ]*/ / +t def +d +:def +s/\\$// +t bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3"/p +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p +d +:bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3\\\\\\n"\\/p +t cont +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p +t cont +d +:cont +n +s/.\{148\}/&'"$ac_delim"'/g +t clear +:clear +s/\\$// +t bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/"/p +d +:bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p +b cont +' >"$CONFIG_STATUS" || ac_write_fail=1 + +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 + for (key in D) D_is_set[key] = 1 + FS = "" +} +/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { + line = \$ 0 + split(line, arg, " ") + if (arg[1] == "#") { + defundef = arg[2] + mac1 = arg[3] + } else { + defundef = substr(arg[1], 2) + mac1 = arg[2] + } + split(mac1, mac2, "(") #) + macro = mac2[1] + prefix = substr(line, 1, index(line, defundef) - 1) + if (D_is_set[macro]) { + suffix = P[macro] D[macro] + while (suffix ~ /[\t ]$/) { + suffix = substr(suffix, 1, length(suffix) - 1) + } + # Preserve the white space surrounding the "#". + print prefix "define", macro suffix + next + } else { + # Replace #undef with comments. This is necessary, for example, + # in the case of _POSIX_SOURCE, which is predefined and required + # on some systems where configure will not decide to define it. + if (defundef == "undef") { + print "/*", prefix defundef, macro, "*/" + next + } + } +} +{ print } +_ACAWK +_ACEOF +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 + as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 +fi # test -n "$CONFIG_HEADERS" + + +eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain ':'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`printf '%s\n' "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is 'configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + printf '%s\n' "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +printf '%s\n' "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`printf '%s\n' "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +printf '%s\n' "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when '$srcdir' = '.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +printf '%s\n' "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + :H) + # + # CONFIG_HEADER + # + if test x"$ac_file" != x-; then + { + printf '%s\n' "/* $configure_input */" >&1 \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" + } >"$ac_tmp/config.h" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 +printf '%s\n' "$as_me: $ac_file is unchanged" >&6;} + else + rm -f "$ac_file" + mv "$ac_tmp/config.h" "$ac_file" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + fi + else + printf '%s\n' "/* $configure_input */" >&1 \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ + || as_fn_error $? "could not create -" "$LINENO" 5 + fi + ;; + + :C) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 +printf '%s\n' "$as_me: executing $ac_file commands" >&6;} + ;; + esac + + + case $ac_file$ac_mode in + "Tcl.framework":C) n=Tcl && + f=$n.framework && v=Versions/$VERSION && + rm -rf $f && mkdir -p $f/$v/Resources && + ln -s $v/$n $v/Resources $f && ln -s ../../../$n $f/$v && + ln -s ../../../../$n-Info.plist $f/$v/Resources/Info.plist && + unset n f v + ;; + + esac +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_CONFIG_STATUS= + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + case $CONFIG_STATUS in #( + -*) : + ac_no_opts=-- ;; #( + *) : + ac_no_opts= ;; +esac + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $ac_no_opts "$CONFIG_STATUS" $ac_config_status_args || + ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + + + + diff --git a/vendor/tcl/macosx/configure.ac b/vendor/tcl/macosx/configure.ac new file mode 100644 index 00000000..6b1e3acb --- /dev/null +++ b/vendor/tcl/macosx/configure.ac @@ -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) diff --git a/vendor/tcl/macosx/license.terms b/vendor/tcl/macosx/license.terms new file mode 100644 index 00000000..d8049cd9 --- /dev/null +++ b/vendor/tcl/macosx/license.terms @@ -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. diff --git a/vendor/tcl/macosx/tclMacOSXBundle.c b/vendor/tcl/macosx/tclMacOSXBundle.c new file mode 100644 index 00000000..40df5356 --- /dev/null +++ b/vendor/tcl/macosx/tclMacOSXBundle.c @@ -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 + * + * 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 + +#include + +#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: + */ diff --git a/vendor/tcl/macosx/tclMacOSXFCmd.c b/vendor/tcl/macosx/tclMacOSXFCmd.c new file mode 100644 index 00000000..d8c6b1c8 --- /dev/null +++ b/vendor/tcl/macosx/tclMacOSXFCmd.c @@ -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 + * + * 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 +#include +#include +#endif + +/* Darwin 8 copyfile API. */ +#ifdef HAVE_COPYFILE +#ifdef HAVE_COPYFILE_H +#include +#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 + +/* + * 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: + */ diff --git a/vendor/tcl/macosx/tclMacOSXNotify.c b/vendor/tcl/macosx/tclMacOSXNotify.c new file mode 100644 index 00000000..81a171a3 --- /dev/null +++ b/vendor/tcl/macosx/tclMacOSXNotify.c @@ -0,0 +1,2089 @@ +/* + * tclMacOSXNotify.c -- + * + * This file contains the implementation of a merged CFRunLoop/select() + * based 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. + * Copyright © 2001-2009, Apple Inc. + * Copyright © 2005-2009 Daniel A. Steffen + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#include "tclInt.h" + +/* + * In macOS 10.12 the os_unfair_lock was introduced as a replacement for the + * OSSpinLock, and the OSSpinLock was deprecated. + */ + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 +#define USE_OS_UNFAIR_LOCK +#include +#endif + +#ifdef HAVE_COREFOUNDATION /* Traditional unix select-based notifier is + * in tclUnixNotfy.c */ +#include +#include + +#if !defined(USE_OS_UNFAIR_LOCK) + +/* + * We use the Darwin-native spinlock API rather than pthread mutexes for + * notifier locking: this radically simplifies the implementation and lowers + * overhead. Note that these are not pure spinlocks, they employ various + * strategies to back off and relinquish the processor, making them immune to + * most priority-inversion livelocks (c.f. 'man 3 OSSpinLockLock' and Darwin + * sources: xnu/osfmk/{ppc,i386}/commpage/spinlocks.s). + */ + +#if defined(HAVE_LIBKERN_OSATOMIC_H) && defined(HAVE_OSSPINLOCKLOCK) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wunused-function" +/* + * Use OSSpinLock API where available (Tiger or later). + */ + +#include + +/* + * Wrappers so that we get warnings in just one small part of this file. + */ + +static inline void +SpinLockLock( + OSSpinLock *lock) +{ + OSSpinLockLock(lock); +} +static inline void +SpinLockUnlock( + OSSpinLock *lock) +{ + OSSpinLockUnlock(lock); +} +static inline bool +SpinLockTry( + OSSpinLock *lock) +{ + return OSSpinLockTry(lock); +} +#define SPINLOCK_INIT OS_SPINLOCK_INIT + +#else +/* + * Otherwise, use commpage spinlock SPI directly. + */ + +typedef uint32_t OSSpinLock; + +static inline void +SpinLockLock( + OSSpinLock *lock) +{ + extern void _spin_lock(OSSpinLock *lock); + + _spin_lock(lock); +} + +static inline void +SpinLockUnlock( + OSSpinLock *lock) +{ + extern void _spin_unlock(OSSpinLock *lock); + + _spin_unlock(lock); +} + +static inline int +SpinLockTry( + OSSpinLock *lock) +{ + extern int _spin_lock_try(OSSpinLock *lock); + + return _spin_lock_try(lock); +} + +#define SPINLOCK_INIT 0 + +#pragma GCC diagnostic pop +#endif /* HAVE_LIBKERN_OSATOMIC_H && HAVE_OSSPINLOCKLOCK */ +#endif /* not using os_unfair_lock */ + +/* + * These locks control access to the global notifier state. + */ + +#if defined(USE_OS_UNFAIR_LOCK) +static os_unfair_lock notifierInitLock = OS_UNFAIR_LOCK_INIT; +static os_unfair_lock notifierLock = OS_UNFAIR_LOCK_INIT; +#else +static OSSpinLock notifierInitLock = SPINLOCK_INIT; +static OSSpinLock notifierLock = SPINLOCK_INIT; +#endif + +/* + * Macros that abstract notifier locking/unlocking + */ + +#if defined(USE_OS_UNFAIR_LOCK) +#define LOCK_NOTIFIER_INIT os_unfair_lock_lock(¬ifierInitLock) +#define UNLOCK_NOTIFIER_INIT os_unfair_lock_unlock(¬ifierInitLock) +#define LOCK_NOTIFIER os_unfair_lock_lock(¬ifierLock) +#define UNLOCK_NOTIFIER os_unfair_lock_unlock(¬ifierLock) +#define LOCK_NOTIFIER_TSD os_unfair_lock_lock(&tsdPtr->tsdLock) +#define UNLOCK_NOTIFIER_TSD os_unfair_lock_unlock(&tsdPtr->tsdLock) +#else +#define LOCK_NOTIFIER_INIT SpinLockLock(¬ifierInitLock) +#define UNLOCK_NOTIFIER_INIT SpinLockUnlock(¬ifierInitLock) +#define LOCK_NOTIFIER SpinLockLock(¬ifierLock) +#define UNLOCK_NOTIFIER SpinLockUnlock(¬ifierLock) +#define LOCK_NOTIFIER_TSD SpinLockLock(&tsdPtr->tsdLock) +#define UNLOCK_NOTIFIER_TSD SpinLockUnlock(&tsdPtr->tsdLock) +#endif + +/* + * This structure is used to keep track of the notifier info for a registered + * file. + */ + +typedef struct FileHandler { + int fd; + int mask; /* Mask of desired events: TCL_READABLE, + * etc. */ + int readyMask; /* Mask of events that have been seen since + * the last time file handlers were invoked + * for this file. */ + Tcl_FileProc *proc; /* Function to call, in the style of + * Tcl_CreateFileHandler. */ + void *clientData; /* Argument to pass to proc. */ + struct FileHandler *nextPtr;/* Next in list of all files we care about. */ +} FileHandler; + +/* + * The following structure is what is added to the Tcl event queue when file + * handlers are ready to fire. + */ + +typedef struct { + Tcl_Event header; /* Information that is standard for all + * events. */ + int fd; /* File descriptor that is ready. Used to find + * the FileHandler structure for the file + * (can't point directly to the FileHandler + * structure because it could go away while + * the event is queued). */ +} FileHandlerEvent; + +/* + * The following structure contains a set of select() masks to track readable, + * writable, and exceptional conditions. + */ + +typedef struct { + fd_set readable; + fd_set writable; + fd_set exceptional; +} SelectMasks; + +/* + * The following static structure contains the state information for the + * select based implementation of the Tcl notifier. One of these structures is + * created for each thread that is using the notifier. + */ + +typedef struct ThreadSpecificData { + FileHandler *firstFileHandlerPtr; + /* Pointer to head of file handler list. */ + int polled; /* True if the notifier thread has polled for + * this thread. */ + int sleeping; /* True if runloop is inside Tcl_Sleep. */ + int runLoopSourcePerformed; /* True after the runLoopSource callack was + * performed. */ + int runLoopRunning; /* True if this thread's Tcl runLoop is + * running. */ + int runLoopNestingLevel; /* Level of nested runLoop invocations. */ + + /* Must hold the notifierLock before accessing the following fields: */ + /* Start notifierLock section */ + int onList; /* True if this thread is on the + * waitingList */ + struct ThreadSpecificData *nextPtr, *prevPtr; + /* All threads that are currently waiting on + * an event have their ThreadSpecificData + * structure on a doubly-linked listed formed + * from these pointers. */ + /* End notifierLock section */ + +#if defined(USE_OS_UNFAIR_LOCK) + os_unfair_lock tsdLock; +#else + OSSpinLock tsdLock; /* Must hold this lock before acessing the + * following fields from more than one + * thread. */ +#endif + + /* Start tsdLock section */ + SelectMasks checkMasks; /* This structure is used to build up the + * masks to be used in the next call to + * select. Bits are set in response to calls + * to Tcl_CreateFileHandler. */ + SelectMasks readyMasks; /* This array reflects the readable/writable + * conditions that were found to exist by the + * last call to select. */ + int numFdBits; /* Number of valid bits in checkMasks (one + * more than highest fd for which + * Tcl_WatchFile has been called). */ + int polling; /* True if this thread is polling for + * events. */ + CFRunLoopRef runLoop; /* This thread's CFRunLoop, needs to be woken + * up whenever the runLoopSource is + * signaled. */ + CFRunLoopSourceRef runLoopSource; + /* Any other thread alerts a notifier that an + * event is ready to be processed by signaling + * this CFRunLoopSource. */ + CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl; + /* Adds/removes this thread from waitingList + * when the CFRunLoop starts/stops. */ + CFRunLoopTimerRef runLoopTimer; + /* Wakes up CFRunLoop after given timeout when + * running embedded. */ + /* End tsdLock section */ + + CFTimeInterval waitTime; /* runLoopTimer wait time when running + * embedded. */ +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +/* + * The following static indicates the number of threads that have initialized + * notifiers. + * + * You must hold the notifierInitLock before accessing this variable. + */ + +static int notifierCount = 0; + +/* + * The following variable points to the head of a doubly-linked list of + * ThreadSpecificData structures for all threads that are currently waiting on + * an event. + * + * You must hold the notifierLock before accessing this list. + */ + +static ThreadSpecificData *waitingListPtr = NULL; + +/* + * The notifier thread spends all its time in select() waiting for a file + * descriptor associated with one of the threads on the waitingListPtr list to + * do something interesting. But if the contents of the waitingListPtr list + * ever changes, we need to wake up and restart the select() system call. You + * can wake up the notifier thread by writing a single byte to the file + * descriptor defined below. This file descriptor is the input-end of a pipe + * and the notifier thread is listening for data on the output-end of the same + * pipe. Hence writing to this file descriptor will cause the select() system + * call to return and wake up the notifier thread. + * + * You must hold the notifierLock lock before writing to the pipe. + */ + +static int triggerPipe = -1; +static int receivePipe = -1; /* Output end of triggerPipe */ + +/* + * The following static indicates if the notifier thread is running. + * + * You must hold the notifierInitLock before accessing this variable. + */ + +static int notifierThreadRunning; + +/* + * The following static flag indicates that async handlers are pending. + */ + +#if TCL_THREADS +static int asyncPending = 0; +#endif + +/* + * Signal mask information for notifier thread. + */ +static sigset_t notifierSigMask; +static sigset_t allSigMask; + +/* + * This is the thread ID of the notifier thread that does select. Only valid + * when notifierThreadRunning is non-zero. + * + * You must hold the notifierInitLock before accessing this variable. + */ + +static pthread_t notifierThread; + +/* + * Custom runloop mode for running with only the runloop source for the + * notifier thread. + */ + +#ifndef TCL_EVENTS_ONLY_RUN_LOOP_MODE +#define TCL_EVENTS_ONLY_RUN_LOOP_MODE "com.tcltk.tclEventsOnlyRunLoopMode" +#endif +#ifdef __CONSTANT_CFSTRINGS__ +#define tclEventsOnlyRunLoopMode CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE) +#else +static CFStringRef tclEventsOnlyRunLoopMode = NULL; +#endif + +/* + * CFTimeInterval to wait forever. + */ + +#define CF_TIMEINTERVAL_FOREVER 5.05e8 + +/* + * Static routines defined in this file. + */ + +static void StartNotifierThread(void); +static TCL_NORETURN void NotifierThreadProc(void *clientData); +static int FileHandlerEventProc(Tcl_Event *evPtr, int flags); +static void TimerWakeUp(CFRunLoopTimerRef timer, void *info); +static void QueueFileEvents(void *info); +static void UpdateWaitingListAndServiceEvents( + CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void *info); +static int OnOffWaitingList(ThreadSpecificData *tsdPtr, + int onList, int signalNotifier); + +#ifdef HAVE_PTHREAD_ATFORK +static int atForkInit = 0; +static void AtForkPrepare(void); +static void AtForkParent(void); +static void AtForkChild(void); + +#endif /* HAVE_PTHREAD_ATFORK */ + +/* + *---------------------------------------------------------------------- + * + * LookUpFileHandler -- + * + * Look up the file handler structure (and optionally the previous one in + * the chain) associated with a file descriptor. + * + * Returns: + * A pointer to the file handler, or NULL if it can't be found. + * + * Side effects: + * If prevPtrPtr is non-NULL, it will be written to if the file handler + * is found. + * + *---------------------------------------------------------------------- + */ + +static inline FileHandler * +LookUpFileHandler( + ThreadSpecificData *tsdPtr, /* Where to look things up. */ + int fd, /* What we are looking for. */ + FileHandler **prevPtrPtr) /* If non-NULL, where to report the previous + * pointer. */ +{ + FileHandler *filePtr, *prevPtr; + + /* + * Find the entry for the given file (and return if there isn't one). + */ + + for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ; + prevPtr = filePtr, filePtr = filePtr->nextPtr) { + if (filePtr == NULL) { + return NULL; + } + if (filePtr->fd == fd) { + break; + } + } + + /* + * Report what we've found to our caller. + */ + + if (prevPtrPtr) { + *prevPtrPtr = prevPtr; + } + return filePtr; +} + +/* + *---------------------------------------------------------------------- + * + * TclpInitNotifier -- + * + * 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); + +#ifdef WEAK_IMPORT_SPINLOCKLOCK + /* + * Initialize support for weakly imported spinlock API. + */ + if (pthread_once(&spinLockLockInitControl, SpinLockLockInit)) { + Tcl_Panic("Tcl_InitNotifier: %s", "pthread_once failed"); + } +#endif + +#ifndef __CONSTANT_CFSTRINGS__ + if (!tclEventsOnlyRunLoopMode) { + tclEventsOnlyRunLoopMode = CFSTR(TCL_EVENTS_ONLY_RUN_LOOP_MODE); + } +#endif + + /* + * Initialize CFRunLoopSource and add it to CFRunLoop of this thread. + */ + + if (!tsdPtr->runLoop) { + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + CFRunLoopSourceRef runLoopSource; + CFRunLoopSourceContext runLoopSourceContext; + CFRunLoopObserverContext runLoopObserverContext; + CFRunLoopObserverRef runLoopObserver, runLoopObserverTcl; + + bzero(&runLoopSourceContext, sizeof(CFRunLoopSourceContext)); + runLoopSourceContext.info = tsdPtr; + runLoopSourceContext.perform = QueueFileEvents; + runLoopSource = CFRunLoopSourceCreate(NULL, LONG_MIN, + &runLoopSourceContext); + if (!runLoopSource) { + Tcl_Panic("Tcl_InitNotifier: %s", + "could not create CFRunLoopSource"); + } + CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopCommonModes); + CFRunLoopAddSource(runLoop, runLoopSource, tclEventsOnlyRunLoopMode); + + bzero(&runLoopObserverContext, sizeof(CFRunLoopObserverContext)); + runLoopObserverContext.info = tsdPtr; + runLoopObserver = CFRunLoopObserverCreate(NULL, + kCFRunLoopEntry|kCFRunLoopExit, TRUE, + LONG_MIN, UpdateWaitingListAndServiceEvents, + &runLoopObserverContext); + if (!runLoopObserver) { + Tcl_Panic("Tcl_InitNotifier: %s", + "could not create CFRunLoopObserver"); + } + CFRunLoopAddObserver(runLoop, runLoopObserver, kCFRunLoopCommonModes); + + /* + * Create a second CFRunLoopObserver with the same callback as above + * for the tclEventsOnlyRunLoopMode to ensure that the callback can be + * re-entered via Tcl_ServiceAll() in the kCFRunLoopBeforeWaiting case + * (CFRunLoop prevents observer callback re-entry of a given observer + * instance). + */ + + runLoopObserverTcl = CFRunLoopObserverCreate(NULL, + kCFRunLoopEntry|kCFRunLoopExit, TRUE, + LONG_MIN, UpdateWaitingListAndServiceEvents, + &runLoopObserverContext); + if (!runLoopObserverTcl) { + Tcl_Panic("Tcl_InitNotifier: %s", + "could not create CFRunLoopObserver"); + } + CFRunLoopAddObserver(runLoop, runLoopObserverTcl, + tclEventsOnlyRunLoopMode); + + tsdPtr->runLoop = runLoop; + tsdPtr->runLoopSource = runLoopSource; + tsdPtr->runLoopObserver = runLoopObserver; + tsdPtr->runLoopObserverTcl = runLoopObserverTcl; + tsdPtr->runLoopTimer = NULL; + tsdPtr->waitTime = CF_TIMEINTERVAL_FOREVER; +#if defined(USE_OS_UNFAIR_LOCK) + tsdPtr->tsdLock = OS_UNFAIR_LOCK_INIT; +#else + tsdPtr->tsdLock = SPINLOCK_INIT; +#endif + } + + LOCK_NOTIFIER_INIT; +#ifdef HAVE_PTHREAD_ATFORK + /* + * Install pthread_atfork handlers to reinitialize the notifier in the + * child of a fork. + */ + + if (!atForkInit) { + int result = pthread_atfork(AtForkPrepare, AtForkParent, AtForkChild); + + if (result) { + Tcl_Panic("Tcl_InitNotifier: %s", "pthread_atfork failed"); + } + atForkInit = 1; + } +#endif /* HAVE_PTHREAD_ATFORK */ + if (notifierCount == 0) { + int fds[2], status; + + /* + * Initialize trigger pipe. + */ + + if (pipe(fds) != 0) { + Tcl_Panic("Tcl_InitNotifier: %s", "could not create trigger pipe"); + } + + status = fcntl(fds[0], F_GETFL); + status |= O_NONBLOCK; + if (fcntl(fds[0], F_SETFL, status) < 0) { + Tcl_Panic("Tcl_InitNotifier: %s", + "could not make receive pipe non-blocking"); + } + status = fcntl(fds[1], F_GETFL); + status |= O_NONBLOCK; + if (fcntl(fds[1], F_SETFL, status) < 0) { + Tcl_Panic("Tcl_InitNotifier: %s", + "could not make trigger pipe non-blocking"); + } + + receivePipe = fds[0]; + triggerPipe = fds[1]; + + /* + * Create notifier thread lazily in Tcl_WaitForEvent() to avoid + * interfering with fork() followed immediately by execve() (we cannot + * execve() when more than one thread is present). + */ + + notifierThreadRunning = 0; + } + notifierCount++; + UNLOCK_NOTIFIER_INIT; + return tsdPtr; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_MacOSXNotifierAddRunLoopMode -- + * + * Add the tcl notifier RunLoop source, observer and timer (if any) + * to the given RunLoop mode. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_MacOSXNotifierAddRunLoopMode( + const void *runLoopMode) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + CFStringRef mode = (CFStringRef) runLoopMode; + + if (tsdPtr->runLoop) { + CFRunLoopAddSource(tsdPtr->runLoop, tsdPtr->runLoopSource, mode); + CFRunLoopAddObserver(tsdPtr->runLoop, tsdPtr->runLoopObserver, mode); + if (tsdPtr->runLoopTimer) { + CFRunLoopAddTimer(tsdPtr->runLoop, tsdPtr->runLoopTimer, mode); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * StartNotifierThread -- + * + * Start notifier thread if necessary. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +StartNotifierThread(void) +{ + LOCK_NOTIFIER_INIT; + if (!notifierCount) { + Tcl_Panic("StartNotifierThread: notifier not initialized"); + } + if (!notifierThreadRunning) { + int result; + pthread_attr_t attr; + + /* + * Arrange for the notifier thread to start with all + * signals blocked. In its mainloop it unblocks the + * signals at safe points. + */ + + sigfillset(&allSigMask); + pthread_sigmask(SIG_BLOCK, &allSigMask, ¬ifierSigMask); + + pthread_attr_init(&attr); + pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_attr_setstacksize(&attr, 60 * 1024); + result = pthread_create(¬ifierThread, &attr, + (void * (*)(void *))(void *)NotifierThreadProc, NULL); + pthread_attr_destroy(&attr); + if (result) { + Tcl_Panic("StartNotifierThread: unable to start notifier thread"); + } + notifierThreadRunning = 1; + + /* + * Restore original signal mask. + */ + + pthread_sigmask(SIG_SETMASK, ¬ifierSigMask, NULL); + } + UNLOCK_NOTIFIER_INIT; +} + +/* + *---------------------------------------------------------------------- + * + * TclpFinalizeNotifier -- + * + * This function is called to cleanup the notifier state before a thread + * is terminated. + * + * Results: + * None. + * + * Side effects: + * May terminate the background notifier thread if this is the last + * notifier instance. + * + *---------------------------------------------------------------------- + */ + +void +TclpFinalizeNotifier( + TCL_UNUSED(void *)) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + LOCK_NOTIFIER_INIT; + notifierCount--; + + /* + * If this is the last thread to use the notifier, close the notifier pipe + * and wait for the background thread to terminate. + */ + + if (notifierCount == 0) { + if (triggerPipe != -1) { + /* + * Send "q" message to the notifier thread so that it will + * terminate. The notifier will return from its call to select() + * and notice that a "q" message has arrived, it will then close + * its side of the pipe and terminate its thread. Note the we can + * not just close the pipe and check for EOF in the notifier + * thread because if a background child process was created with + * exec, select() would not register the EOF on the pipe until the + * child processes had terminated. [Bug: 4139] [Bug 1222872] + */ + + write(triggerPipe, "q", 1); + close(triggerPipe); + + if (notifierThreadRunning) { + int result = pthread_join(notifierThread, NULL); + + if (result) { + Tcl_Panic("Tcl_FinalizeNotifier: unable to join notifier " + "thread"); + } + notifierThreadRunning = 0; + + /* + * If async marks are outstanding, perform actions now. + */ + if (asyncPending) { + asyncPending = 0; + TclAsyncMarkFromNotifier(); + } + } + + close(receivePipe); + triggerPipe = -1; + } + } + UNLOCK_NOTIFIER_INIT; + + LOCK_NOTIFIER_TSD; /* For concurrency with Tcl_AlertNotifier */ + if (tsdPtr->runLoop) { + tsdPtr->runLoop = NULL; + + /* + * Remove runLoopSource, runLoopObserver and runLoopTimer from all + * CFRunLoops. + */ + + CFRunLoopSourceInvalidate(tsdPtr->runLoopSource); + CFRelease(tsdPtr->runLoopSource); + tsdPtr->runLoopSource = NULL; + CFRunLoopObserverInvalidate(tsdPtr->runLoopObserver); + CFRelease(tsdPtr->runLoopObserver); + tsdPtr->runLoopObserver = NULL; + CFRunLoopObserverInvalidate(tsdPtr->runLoopObserverTcl); + CFRelease(tsdPtr->runLoopObserverTcl); + tsdPtr->runLoopObserverTcl = NULL; + if (tsdPtr->runLoopTimer) { + CFRunLoopTimerInvalidate(tsdPtr->runLoopTimer); + CFRelease(tsdPtr->runLoopTimer); + tsdPtr->runLoopTimer = NULL; + } + } + UNLOCK_NOTIFIER_TSD; +} + +/* + *---------------------------------------------------------------------- + * + * 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. + * + * Results: + * None. + * + * Side effects: + * Signals the notifier condition variable for the specified notifier. + * + *---------------------------------------------------------------------- + */ + +void +TclpAlertNotifier( + void *clientData) +{ + ThreadSpecificData *tsdPtr = (ThreadSpecificData *)clientData; + + LOCK_NOTIFIER_TSD; + if (tsdPtr->runLoop) { + CFRunLoopSourceSignal(tsdPtr->runLoopSource); + CFRunLoopWakeUp(tsdPtr->runLoop); + } + UNLOCK_NOTIFIER_TSD; +} + +/* + *---------------------------------------------------------------------- + * + * TclpSetTimer -- + * + * This function sets the current notifier timer value. + * + * Results: + * None. + * + * Side effects: + * Replaces any previous timer. + * + *---------------------------------------------------------------------- + */ + +void +TclpSetTimer( + const Tcl_Time *timePtr) /* Timeout value, may be NULL. */ +{ + ThreadSpecificData *tsdPtr; + CFRunLoopTimerRef runLoopTimer; + CFTimeInterval waitTime; + + tsdPtr = TCL_TSD_INIT(&dataKey); + runLoopTimer = tsdPtr->runLoopTimer; + if (!runLoopTimer) { + return; + } + if (timePtr) { + Tcl_Time vTime = *timePtr; + + if (vTime.sec != 0 || vTime.usec != 0) { + TclScaleTime(&vTime); + waitTime = vTime.sec + 1.0e-6 * vTime.usec; + } else { + waitTime = 0; + } + } else { + waitTime = CF_TIMEINTERVAL_FOREVER; + } + tsdPtr->waitTime = waitTime; + CFRunLoopTimerSetNextFireDate(runLoopTimer, + CFAbsoluteTimeGetCurrent() + waitTime); +} + +/* + *---------------------------------------------------------------------- + * + * TimerWakeUp -- + * + * CFRunLoopTimer callback. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +TimerWakeUp( + TCL_UNUSED(CFRunLoopTimerRef), + TCL_UNUSED(void *)) +{ +} + +/* + *---------------------------------------------------------------------- + * + * TclpServiceModeHook -- + * + * This function is invoked whenever the service mode changes. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclpServiceModeHook( + int mode) /* Either TCL_SERVICE_ALL, or + * TCL_SERVICE_NONE. */ +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (mode == TCL_SERVICE_ALL && !tsdPtr->runLoopTimer) { + if (!tsdPtr->runLoop) { + Tcl_Panic("Tcl_ServiceModeHook: Notifier not initialized"); + } + tsdPtr->runLoopTimer = CFRunLoopTimerCreate(NULL, + CFAbsoluteTimeGetCurrent() + CF_TIMEINTERVAL_FOREVER, + CF_TIMEINTERVAL_FOREVER, 0, 0, TimerWakeUp, NULL); + if (tsdPtr->runLoopTimer) { + CFRunLoopAddTimer(tsdPtr->runLoop, tsdPtr->runLoopTimer, + kCFRunLoopCommonModes); + StartNotifierThread(); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreateFileHandler -- + * + * This function registers a file handler with the notifier. + * + * Results: + * None. + * + * Side effects: + * Creates a new file handler structure. + * + *---------------------------------------------------------------------- + */ + +void +TclpCreateFileHandler( + int fd, /* Handle of stream to watch. */ + int mask, /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, and TCL_EXCEPTION: indicates + * conditions under which proc should be + * called. */ + Tcl_FileProc *proc, /* Function to call for each selected + * event. */ + void *clientData) /* Arbitrary data to pass to proc. */ +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + FileHandler *filePtr = LookUpFileHandler(tsdPtr, fd, NULL); + + if (filePtr == NULL) { + filePtr = (FileHandler *)Tcl_Alloc(sizeof(FileHandler)); + filePtr->fd = fd; + filePtr->readyMask = 0; + filePtr->nextPtr = tsdPtr->firstFileHandlerPtr; + tsdPtr->firstFileHandlerPtr = filePtr; + } + filePtr->proc = proc; + filePtr->clientData = clientData; + filePtr->mask = mask; + + /* + * Update the check masks for this file. + */ + + LOCK_NOTIFIER_TSD; + if (mask & TCL_READABLE) { + FD_SET(fd, &tsdPtr->checkMasks.readable); + } else { + FD_CLR(fd, &tsdPtr->checkMasks.readable); + } + if (mask & TCL_WRITABLE) { + FD_SET(fd, &tsdPtr->checkMasks.writable); + } else { + FD_CLR(fd, &tsdPtr->checkMasks.writable); + } + if (mask & TCL_EXCEPTION) { + FD_SET(fd, &tsdPtr->checkMasks.exceptional); + } else { + FD_CLR(fd, &tsdPtr->checkMasks.exceptional); + } + if (tsdPtr->numFdBits <= fd) { + tsdPtr->numFdBits = fd+1; + } + UNLOCK_NOTIFIER_TSD; +} + +/* + *---------------------------------------------------------------------- + * + * TclpDeleteFileHandler -- + * + * Cancel a previously-arranged callback arrangement for a file. + * + * Results: + * None. + * + * Side effects: + * If a callback was previously registered on file, remove it. + * + *---------------------------------------------------------------------- + */ + +void +TclpDeleteFileHandler( + int fd) /* Stream id for which to remove callback + * function. */ +{ + FileHandler *filePtr, *prevPtr; + int i, numFdBits = -1; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * Find the entry for the given file (and return if there isn't one). + */ + + filePtr = LookUpFileHandler(tsdPtr, fd, &prevPtr); + if (filePtr == NULL) { + return; + } + + /* + * Find current max fd. + */ + + if (fd + 1 == tsdPtr->numFdBits) { + numFdBits = 0; + for (i = fd - 1; i >= 0; i--) { + if (FD_ISSET(i, &tsdPtr->checkMasks.readable) + || FD_ISSET(i, &tsdPtr->checkMasks.writable) + || FD_ISSET(i, &tsdPtr->checkMasks.exceptional)) { + numFdBits = i + 1; + break; + } + } + } + + LOCK_NOTIFIER_TSD; + if (numFdBits != -1) { + tsdPtr->numFdBits = numFdBits; + } + + /* + * Update the check masks for this file. + */ + + if (filePtr->mask & TCL_READABLE) { + FD_CLR(fd, &tsdPtr->checkMasks.readable); + } + if (filePtr->mask & TCL_WRITABLE) { + FD_CLR(fd, &tsdPtr->checkMasks.writable); + } + if (filePtr->mask & TCL_EXCEPTION) { + FD_CLR(fd, &tsdPtr->checkMasks.exceptional); + } + UNLOCK_NOTIFIER_TSD; + + /* + * Clean up information in the callback record. + */ + + if (prevPtr == NULL) { + tsdPtr->firstFileHandlerPtr = filePtr->nextPtr; + } else { + prevPtr->nextPtr = filePtr->nextPtr; + } + Tcl_Free(filePtr); +} + +/* + *---------------------------------------------------------------------- + * + * FileHandlerEventProc -- + * + * This function is called by Tcl_ServiceEvent when a file event reaches + * the front of the event queue. This function is responsible for + * actually handling the event by invoking the callback for the file + * handler. + * + * Results: + * Returns 1 if the event was handled, meaning it should be removed from + * the queue. Returns 0 if the event was not handled, meaning it should + * stay on the queue. The only time the event isn't handled is if the + * TCL_FILE_EVENTS flag bit isn't set. + * + * Side effects: + * Whatever the file handler's callback function does. + * + *---------------------------------------------------------------------- + */ + +static int +FileHandlerEventProc( + Tcl_Event *evPtr, /* Event to service. */ + int flags) /* Flags that indicate what events to handle, + * such as TCL_FILE_EVENTS. */ +{ + int mask; + FileHandler *filePtr; + FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr; + ThreadSpecificData *tsdPtr; + + if (!(flags & TCL_FILE_EVENTS)) { + return 0; + } + + /* + * Search through the file handlers to find the one whose handle matches + * the event. We do this rather than keeping a pointer to the file handler + * directly in the event, so that the handler can be deleted while the + * event is queued without leaving a dangling pointer. + */ + + tsdPtr = TCL_TSD_INIT(&dataKey); + filePtr = LookUpFileHandler(tsdPtr, fileEvPtr->fd, NULL); + if (filePtr != NULL) { + /* + * The code is tricky for two reasons: + * 1. The file handler's desired events could have changed since the + * time when the event was queued, so AND the ready mask with the + * desired mask. + * 2. The file could have been closed and re-opened since the time + * when the event was queued. This is why the ready mask is stored + * in the file handler rather than the queued event: it will be + * zeroed when a new file handler is created for the newly opened + * file. + */ + + mask = filePtr->readyMask & filePtr->mask; + filePtr->readyMask = 0; + if (mask != 0) { + LOCK_NOTIFIER_TSD; + if (mask & TCL_READABLE) { + FD_CLR(filePtr->fd, &tsdPtr->readyMasks.readable); + } + if (mask & TCL_WRITABLE) { + FD_CLR(filePtr->fd, &tsdPtr->readyMasks.writable); + } + if (mask & TCL_EXCEPTION) { + FD_CLR(filePtr->fd, &tsdPtr->readyMasks.exceptional); + } + UNLOCK_NOTIFIER_TSD; + filePtr->proc(filePtr->clientData, mask); + } + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * TclpNotifierData -- + * + * This function returns a void pointer to be associated + * with a Tcl_AsyncHandler. + * + * Results: + * On MacOSX, 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 without blocking. + * + * Results: + * Returns 0 if a tcl event or timeout occurred and 1 if a non-tcl + * CFRunLoop source was processed. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclpWaitForEvent( + const Tcl_Time *timePtr) /* Maximum block time, or NULL. */ +{ + int result, polling, runLoopRunning; + CFTimeInterval waitTime; + SInt32 runLoopStatus; + ThreadSpecificData *tsdPtr; + + result = -1; + polling = 0; + waitTime = CF_TIMEINTERVAL_FOREVER; + tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!tsdPtr->runLoop) { + Tcl_Panic("Tcl_WaitForEvent: Notifier not initialized"); + } + + /* + * A NULL timePtr means wait forever. + */ + + if (timePtr) { + Tcl_Time vTime = *timePtr; + + /* + * TIP #233 (Virtualized Time). Is virtual time in effect? And do we + * actually have something to scale? If yes to both then we call the + * handler to do this scaling. + */ + + if (vTime.sec != 0 || vTime.usec != 0) { + TclScaleTime(&vTime); + waitTime = vTime.sec + 1.0e-6 * vTime.usec; + } else { + /* + * The max block time was set to 0. + * + * If we set the waitTime to 0, then the call to CFRunLoopInMode + * may return without processing all of its sources. The Apple + * documentation says that if the waitTime is 0 "only one pass is + * made through the run loop before returning; if multiple sources + * or timers are ready to fire immediately, only one (possibly two + * if one is a version 0 source) will be fired, regardless of the + * value of returnAfterSourceHandled." This can cause some chanio + * tests to fail. So we use a small positive waitTime unless + * there is another RunLoop running. + */ + + polling = 1; + waitTime = tsdPtr->runLoopRunning ? 0 : 0.0001; + } + } + + StartNotifierThread(); + + LOCK_NOTIFIER_TSD; + tsdPtr->polling = polling; + UNLOCK_NOTIFIER_TSD; + tsdPtr->runLoopSourcePerformed = 0; + + /* + * If the Tcl runloop is already running (e.g. if Tcl_WaitForEvent was + * called recursively) start a new runloop in a custom runloop mode + * containing only the source for the notifier thread. Otherwise wakeups + * from other sources added to the common runloop mode might get lost or + * 3rd party event handlers might get called when they do not expect to + * be. + */ + + runLoopRunning = tsdPtr->runLoopRunning; + tsdPtr->runLoopRunning = 1; + runLoopStatus = CFRunLoopRunInMode( + runLoopRunning ? tclEventsOnlyRunLoopMode : kCFRunLoopDefaultMode, + waitTime, TRUE); + tsdPtr->runLoopRunning = runLoopRunning; + + LOCK_NOTIFIER_TSD; + tsdPtr->polling = 0; + UNLOCK_NOTIFIER_TSD; + switch (runLoopStatus) { + case kCFRunLoopRunFinished: + Tcl_Panic("Tcl_WaitForEvent: CFRunLoop finished"); + break; + case kCFRunLoopRunTimedOut: + QueueFileEvents(tsdPtr); + result = 0; + break; + case kCFRunLoopRunStopped: + case kCFRunLoopRunHandledSource: + result = tsdPtr->runLoopSourcePerformed ? 0 : 1; + break; + } + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * QueueFileEvents -- + * + * CFRunLoopSource callback for queueing file events. + * + * Results: + * None. + * + * Side effects: + * Queues file events that are detected by the select. + * + *---------------------------------------------------------------------- + */ + +static void +QueueFileEvents( + void *info) +{ + SelectMasks readyMasks; + FileHandler *filePtr; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *)info; + + /* + * Queue all detected file events. + */ + + LOCK_NOTIFIER_TSD; + FD_COPY(&tsdPtr->readyMasks.readable, &readyMasks.readable); + FD_COPY(&tsdPtr->readyMasks.writable, &readyMasks.writable); + FD_COPY(&tsdPtr->readyMasks.exceptional, &readyMasks.exceptional); + FD_ZERO(&tsdPtr->readyMasks.readable); + FD_ZERO(&tsdPtr->readyMasks.writable); + FD_ZERO(&tsdPtr->readyMasks.exceptional); + UNLOCK_NOTIFIER_TSD; + tsdPtr->runLoopSourcePerformed = 1; + + for (filePtr = tsdPtr->firstFileHandlerPtr; (filePtr != NULL); + filePtr = filePtr->nextPtr) { + int mask = 0; + + if (FD_ISSET(filePtr->fd, &readyMasks.readable)) { + mask |= TCL_READABLE; + } + if (FD_ISSET(filePtr->fd, &readyMasks.writable)) { + mask |= TCL_WRITABLE; + } + if (FD_ISSET(filePtr->fd, &readyMasks.exceptional)) { + mask |= TCL_EXCEPTION; + } + if (!mask) { + continue; + } + + /* + * Don't bother to queue an event if the mask was previously non-zero + * since an event must still be on the queue. + */ + + if (filePtr->readyMask == 0) { + FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) + Tcl_Alloc(sizeof(FileHandlerEvent)); + + fileEvPtr->header.proc = FileHandlerEventProc; + fileEvPtr->fd = filePtr->fd; + Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL); + } + filePtr->readyMask = mask; + } +} + +/* + *---------------------------------------------------------------------- + * + * UpdateWaitingListAndServiceEvents -- + * + * CFRunLoopObserver callback for updating waitingList and servicing Tcl + * events. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +UpdateWaitingListAndServiceEvents( + TCL_UNUSED(CFRunLoopObserverRef), + CFRunLoopActivity activity, + void *info) +{ + ThreadSpecificData *tsdPtr = (ThreadSpecificData *)info; + + if (tsdPtr->sleeping) { + return; + } + switch (activity) { + case kCFRunLoopEntry: + tsdPtr->runLoopNestingLevel++; + if (tsdPtr->numFdBits > 0 || tsdPtr->polling) { + LOCK_NOTIFIER; + if (!OnOffWaitingList(tsdPtr, 1, 1) && tsdPtr->polling) { + write(triggerPipe, "", 1); + } + UNLOCK_NOTIFIER; + } + break; + case kCFRunLoopExit: + if (tsdPtr->runLoopNestingLevel == 1) { + LOCK_NOTIFIER; + OnOffWaitingList(tsdPtr, 0, 1); + UNLOCK_NOTIFIER; + } + tsdPtr->runLoopNestingLevel--; + break; + default: + break; + } +} + +/* + *---------------------------------------------------------------------- + * + * OnOffWaitingList -- + * + * Add/remove the specified thread to/from the global waitingList and + * optionally signal the notifier. + * + * !!! Requires notifierLock to be held !!! + * + * Results: + * Boolean indicating whether the waitingList was changed. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +OnOffWaitingList( + ThreadSpecificData *tsdPtr, + int onList, + int signalNotifier) +{ + int changeWaitingList; + + changeWaitingList = (!onList ^ !tsdPtr->onList); + if (changeWaitingList) { + if (onList) { + tsdPtr->nextPtr = waitingListPtr; + if (waitingListPtr) { + waitingListPtr->prevPtr = tsdPtr; + } + tsdPtr->prevPtr = NULL; + waitingListPtr = tsdPtr; + tsdPtr->onList = 1; + } else { + if (tsdPtr->prevPtr) { + tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; + } else { + waitingListPtr = tsdPtr->nextPtr; + } + if (tsdPtr->nextPtr) { + tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; + } + tsdPtr->nextPtr = tsdPtr->prevPtr = NULL; + tsdPtr->onList = 0; + } + if (signalNotifier) { + write(triggerPipe, "", 1); + } + } + + return changeWaitingList; +} + +/* + *---------------------------------------------------------------------- + * + * 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. */ +{ + Tcl_Time vdelay; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (ms <= 0) { + return; + } + + /* + * TIP #233: Scale from virtual time to real-time. + */ + + vdelay.sec = ms / 1000; + vdelay.usec = (ms % 1000) * 1000; + TclScaleTime(&vdelay); + + if (tsdPtr->runLoop) { + CFTimeInterval waitTime; + CFRunLoopTimerRef runLoopTimer = tsdPtr->runLoopTimer; + CFAbsoluteTime nextTimerFire = 0, waitEnd, now; + SInt32 runLoopStatus; + + waitTime = vdelay.sec + 1.0e-6 * vdelay.usec; + now = CFAbsoluteTimeGetCurrent(); + waitEnd = now + waitTime; + + if (runLoopTimer) { + nextTimerFire = CFRunLoopTimerGetNextFireDate(runLoopTimer); + if (nextTimerFire < waitEnd) { + CFRunLoopTimerSetNextFireDate(runLoopTimer, now + + CF_TIMEINTERVAL_FOREVER); + } else { + runLoopTimer = NULL; + } + } + tsdPtr->sleeping = 1; + do { + runLoopStatus = CFRunLoopRunInMode(kCFRunLoopDefaultMode, + waitTime, FALSE); + switch (runLoopStatus) { + case kCFRunLoopRunFinished: + Tcl_Panic("Tcl_Sleep: CFRunLoop finished"); + break; + case kCFRunLoopRunStopped: + waitTime = waitEnd - CFAbsoluteTimeGetCurrent(); + break; + case kCFRunLoopRunTimedOut: + waitTime = 0; + break; + } + } while (waitTime > 0); + tsdPtr->sleeping = 0; + if (runLoopTimer) { + CFRunLoopTimerSetNextFireDate(runLoopTimer, nextTimerFire); + } + } else { + struct timespec waitTime; + + waitTime.tv_sec = vdelay.sec; + waitTime.tv_nsec = vdelay.usec * 1000; + while (nanosleep(&waitTime, &waitTime)) { + /* Empty body */ + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TclUnixWaitForFile -- + * + * This function waits synchronously for a file to become readable or + * writable, with an optional timeout. + * + * Results: + * The return value is an OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, and TCL_EXCEPTION, indicating the conditions that are + * present on file at the time of the return. This function will not + * return until either "timeout" milliseconds have elapsed or at least + * one of the conditions given by mask has occurred for file (a return + * value of 0 means that a timeout occurred). No normal events will be + * serviced during the execution of this function. + * + * Side effects: + * Time passes. + * + *---------------------------------------------------------------------- + */ + +int +TclUnixWaitForFile( + int fd, /* Handle for file on which to wait. */ + int mask, /* What to wait for: OR'ed combination of + * TCL_READABLE, TCL_WRITABLE, and + * TCL_EXCEPTION. */ + int timeout) /* Maximum amount of time to wait for one of + * the conditions in mask to occur, in + * milliseconds. A value of 0 means don't wait + * at all, and a value of -1 means wait + * forever. */ +{ + Tcl_Time abortTime = {0, 0}, now; /* silence gcc 4 warning */ + struct timeval blockTime, *timeoutPtr; + int numFound, result = 0; + fd_set readableMask; + fd_set writableMask; + fd_set exceptionalMask; + +#define SET_BITS(var, bits) ((var) |= (bits)) +#define CLEAR_BITS(var, bits) ((var) &= ~(bits)) + +#ifndef _DARWIN_C_SOURCE + /* + * Sanity check fd. + */ + + if (fd >= FD_SETSIZE) { + Tcl_Panic("TclUnixWaitForFile cannot handle file id %d", fd); + /* must never get here, or select masks overrun will occur below */ + } +#endif + + /* + * If there is a non-zero finite timeout, compute the time when we give + * up. + */ + + if (timeout > 0) { + Tcl_GetTime(&now); + abortTime.sec = now.sec + timeout/1000; + abortTime.usec = now.usec + (timeout%1000)*1000; + if (abortTime.usec >= 1000000) { + abortTime.usec -= 1000000; + abortTime.sec += 1; + } + timeoutPtr = &blockTime; + } else if (timeout == 0) { + timeoutPtr = &blockTime; + blockTime.tv_sec = 0; + blockTime.tv_usec = 0; + } else { + timeoutPtr = NULL; + } + + /* + * Initialize the select masks. + */ + + FD_ZERO(&readableMask); + FD_ZERO(&writableMask); + FD_ZERO(&exceptionalMask); + + /* + * Loop in a mini-event loop of our own, waiting for either the file to + * become ready or a timeout to occur. + */ + + while (1) { + if (timeout > 0) { + blockTime.tv_sec = abortTime.sec - now.sec; + blockTime.tv_usec = abortTime.usec - now.usec; + if (blockTime.tv_usec < 0) { + blockTime.tv_sec -= 1; + blockTime.tv_usec += 1000000; + } + if (blockTime.tv_sec < 0) { + blockTime.tv_sec = 0; + blockTime.tv_usec = 0; + } + } + + /* + * Setup the select masks for the fd. + */ + + if (mask & TCL_READABLE) { + FD_SET(fd, &readableMask); + } + if (mask & TCL_WRITABLE) { + FD_SET(fd, &writableMask); + } + if (mask & TCL_EXCEPTION) { + FD_SET(fd, &exceptionalMask); + } + + /* + * Wait for the event or a timeout. + */ + + numFound = select(fd + 1, &readableMask, &writableMask, + &exceptionalMask, timeoutPtr); + if (numFound == 1) { + if (FD_ISSET(fd, &readableMask)) { + SET_BITS(result, TCL_READABLE); + } + if (FD_ISSET(fd, &writableMask)) { + SET_BITS(result, TCL_WRITABLE); + } + if (FD_ISSET(fd, &exceptionalMask)) { + SET_BITS(result, TCL_EXCEPTION); + } + result &= mask; + if (result) { + break; + } + } + if (timeout == 0) { + break; + } + if (timeout < 0) { + continue; + } + + /* + * The select returned early, so we need to recompute the timeout. + */ + + Tcl_GetTime(&now); + if ((abortTime.sec < now.sec) + || (abortTime.sec==now.sec && abortTime.usec<=now.usec)) { + break; + } + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclAsyncNotifier -- + * + * This procedure sets the async mark of an async handler to a + * given value, if it is called from the notifier thread. + * + * Result: + * True, when the handler will be marked, false otherwise. + * + * Side effetcs: + * The trigger pipe is written when called from the notifier + * thread. + * + *---------------------------------------------------------------------- + */ + +int +TclAsyncNotifier( + int sigNumber, /* Signal number. */ + TCL_UNUSED(Tcl_ThreadId), /* Target thread. */ + TCL_UNUSED(void *), /* Notifier data. */ + int *flagPtr, /* Flag to mark. */ + int value) /* Value of mark. */ +{ +#if TCL_THREADS + /* + * WARNING: + * This code most likely runs in a signal handler. Thus, + * only few async-signal-safe system calls are allowed, + * e.g. pthread_self(), sem_post(), write(). + */ + + if (pthread_equal(pthread_self(), (pthread_t) notifierThread)) { + if (notifierThreadRunning) { + *flagPtr = value; + if (!asyncPending) { + asyncPending = 1; + write(triggerPipe, "S", 1); + } + return 1; + } + return 0; + } + + /* + * Re-send the signal to the notifier thread. + */ + + pthread_kill((pthread_t) notifierThread, sigNumber); +#endif + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * NotifierThreadProc -- + * + * This routine is the initial (and only) function executed by the + * special notifier thread. Its job is to wait for file descriptors to + * become readable or writable or to have an exception condition and then + * to notify other threads who are interested in this information by + * signalling a condition variable. Other threads can signal this + * notifier thread of a change in their interests by writing a single + * byte to a special pipe that the notifier thread is monitoring. + * + * Result: + * None. Once started, this routine never exits. It dies with the overall + * process or terminates its own thread (on notifier termination). + * + * Side effects: + * The trigger pipe used to signal the notifier thread is created when + * the notifier thread first starts. + * + *---------------------------------------------------------------------- + */ + +static TCL_NORETURN void +NotifierThreadProc( + TCL_UNUSED(void *)) +{ + ThreadSpecificData *tsdPtr; + fd_set readableMask, writableMask, exceptionalMask; + int i, ret, numFdBits = 0, polling; + struct timeval poll = {0., 0.}, *timePtr; + char buf[2]; + + /* + * Look for file events and report them to interested threads. + */ + + while (1) { + FD_ZERO(&readableMask); + FD_ZERO(&writableMask); + FD_ZERO(&exceptionalMask); + + /* + * Compute the logical OR of the select masks from all the waiting + * notifiers. + */ + + timePtr = NULL; + LOCK_NOTIFIER; + for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { + LOCK_NOTIFIER_TSD; + for (i = tsdPtr->numFdBits-1; i >= 0; --i) { + if (FD_ISSET(i, &tsdPtr->checkMasks.readable)) { + FD_SET(i, &readableMask); + } + if (FD_ISSET(i, &tsdPtr->checkMasks.writable)) { + FD_SET(i, &writableMask); + } + if (FD_ISSET(i, &tsdPtr->checkMasks.exceptional)) { + FD_SET(i, &exceptionalMask); + } + } + if (tsdPtr->numFdBits > numFdBits) { + numFdBits = tsdPtr->numFdBits; + } + polling = tsdPtr->polling; + UNLOCK_NOTIFIER_TSD; + if ((tsdPtr->polled = polling)) { + timePtr = &poll; + } + } + UNLOCK_NOTIFIER; + + /* + * Set up the select mask to include the receive pipe. + */ + + if (receivePipe >= numFdBits) { + numFdBits = receivePipe + 1; + } + FD_SET(receivePipe, &readableMask); + + /* + * Signals are unblocked only during select(). + */ + + pthread_sigmask(SIG_SETMASK, ¬ifierSigMask, NULL); + ret = select(numFdBits, &readableMask, &writableMask, &exceptionalMask, + timePtr); + pthread_sigmask(SIG_BLOCK, &allSigMask, NULL); + + if (ret == -1) { + /* + * In case a signal was caught during select(), + * perform work on async handlers now. + */ + if (errno == EINTR && asyncPending) { + asyncPending = 0; + TclAsyncMarkFromNotifier(); + } + + /* + * Try again immediately on an error. + */ + + continue; + } + + /* + * Alert any threads that are waiting on a ready file descriptor. + */ + + LOCK_NOTIFIER; + for (tsdPtr = waitingListPtr; tsdPtr; tsdPtr = tsdPtr->nextPtr) { + int found = 0; + SelectMasks readyMasks, checkMasks; + + LOCK_NOTIFIER_TSD; + FD_COPY(&tsdPtr->checkMasks.readable, &checkMasks.readable); + FD_COPY(&tsdPtr->checkMasks.writable, &checkMasks.writable); + FD_COPY(&tsdPtr->checkMasks.exceptional, &checkMasks.exceptional); + UNLOCK_NOTIFIER_TSD; + found = tsdPtr->polled; + FD_ZERO(&readyMasks.readable); + FD_ZERO(&readyMasks.writable); + FD_ZERO(&readyMasks.exceptional); + + for (i = tsdPtr->numFdBits-1; i >= 0; --i) { + if (FD_ISSET(i, &checkMasks.readable) + && FD_ISSET(i, &readableMask)) { + FD_SET(i, &readyMasks.readable); + found = 1; + } + if (FD_ISSET(i, &checkMasks.writable) + && FD_ISSET(i, &writableMask)) { + FD_SET(i, &readyMasks.writable); + found = 1; + } + if (FD_ISSET(i, &checkMasks.exceptional) + && FD_ISSET(i, &exceptionalMask)) { + FD_SET(i, &readyMasks.exceptional); + found = 1; + } + } + + if (found) { + /* + * Remove the ThreadSpecificData structure of this thread from + * the waiting list. This prevents us from spinning + * continuously on select until the other threads runs and + * services the file event. + */ + + OnOffWaitingList(tsdPtr, 0, 0); + + LOCK_NOTIFIER_TSD; + FD_COPY(&readyMasks.readable, &tsdPtr->readyMasks.readable); + FD_COPY(&readyMasks.writable, &tsdPtr->readyMasks.writable); + FD_COPY(&readyMasks.exceptional, + &tsdPtr->readyMasks.exceptional); + UNLOCK_NOTIFIER_TSD; + tsdPtr->polled = 0; + if (tsdPtr->runLoop) { + CFRunLoopSourceSignal(tsdPtr->runLoopSource); + CFRunLoopWakeUp(tsdPtr->runLoop); + } + } + } + UNLOCK_NOTIFIER; + + /* + * Consume the next byte from the notifier pipe if the pipe was + * readable. Note that there may be multiple bytes pending, but to + * avoid a race condition we only read one at a time. + */ + + if (FD_ISSET(receivePipe, &readableMask)) { + i = (int)read(receivePipe, buf, 1); + + if ((i == 0) || ((i == 1) && (buf[0] == 'q'))) { + /* + * Someone closed the write end of the pipe or sent us a Quit + * message [Bug: 4139] and then closed the write end of the + * pipe so we need to shut down the notifier thread. + */ + + break; + } + + if (asyncPending) { + asyncPending = 0; + TclAsyncMarkFromNotifier(); + } + } + } + pthread_exit(0); +} + +#ifdef HAVE_PTHREAD_ATFORK +/* + *---------------------------------------------------------------------- + * + * AtForkPrepare -- + * + * Lock the notifier in preparation for a fork. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +AtForkPrepare(void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + LOCK_NOTIFIER_INIT; + LOCK_NOTIFIER; + LOCK_NOTIFIER_TSD; +} + +/* + *---------------------------------------------------------------------- + * + * AtForkParent -- + * + * Unlock the notifier in the parent after a fork. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +AtForkParent(void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + UNLOCK_NOTIFIER_TSD; + UNLOCK_NOTIFIER; + UNLOCK_NOTIFIER_INIT; +} + +/* + *---------------------------------------------------------------------- + * + * AtForkChild -- + * + * Unlock and reinstall the notifier in the child after a fork. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +AtForkChild(void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * If a child process unlocks an os_unfair_lock that was created in its + * parent the child will exit with an illegal instruction error. So we + * reinitialize the lock in the child rather than attempt to unlock it. + */ + +#if defined(USE_OS_UNFAIR_LOCK) + notifierInitLock = OS_UNFAIR_LOCK_INIT; + notifierLock = OS_UNFAIR_LOCK_INIT; + tsdPtr->tsdLock = OS_UNFAIR_LOCK_INIT; +#else + UNLOCK_NOTIFIER_TSD; + UNLOCK_NOTIFIER; + UNLOCK_NOTIFIER_INIT; +#endif + + asyncPending = 0; + + if (tsdPtr->runLoop) { + tsdPtr->runLoop = NULL; + tsdPtr->runLoopSource = NULL; + tsdPtr->runLoopTimer = NULL; + } + if (notifierCount > 0) { + notifierCount = 1; + notifierThreadRunning = 0; + + /* + * Restart the notifier thread for signal handling. + */ + + StartNotifierThread(); + } +} +#endif /* HAVE_PTHREAD_ATFORK */ + +#else /* HAVE_COREFOUNDATION */ + +void +Tcl_MacOSXNotifierAddRunLoopMode( + const void *runLoopMode) +{ + Tcl_Panic("Tcl_MacOSXNotifierAddRunLoopMode: " + "Tcl not built with CoreFoundation support"); +} + +#endif /* HAVE_COREFOUNDATION */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/Makefile.in b/vendor/tcl/win/Makefile.in new file mode 100644 index 00000000..3d023fc5 --- /dev/null +++ b/vendor/tcl/win/Makefile.in @@ -0,0 +1,1231 @@ +# +# This file is a Makefile for Tcl. If it has the name "Makefile.in" then it +# is a template for a Makefile; to generate the actual Makefile, run +# "./configure", which is a configuration script generated by the "autoconf" +# program (constructs like "@foo@" will get replaced in the actual Makefile. + +VERSION = @TCL_VERSION@ + +#-------------------------------------------------------------------------- +# Things you can change to personalize the Makefile for your own site (you can +# make these changes in either Makefile.in or Makefile, but changes to +# Makefile will get lost if you re-run the configuration script). +#-------------------------------------------------------------------------- + +# Default top-level directories in which to install architecture-specific +# files (exec_prefix) and machine-independent files such as scripts (prefix). +# The values specified here may be overridden at configure-time with the +# --exec-prefix and --prefix options to the "configure" script. + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +includedir = @includedir@ +datarootdir = @datarootdir@ +runstatedir = @runstatedir@ +mandir = @mandir@ + +# Configure arguments +PKG_CFG_ARGS = @PKG_CFG_ARGS@ + +# The following definition can be set to non-null for special systems like AFS +# with replication. It allows the pathnames used for installation to be +# different than those used for actually reference files at run-time. +# INSTALL_ROOT is prepended to $prefix and $exec_prefix when installing files. +INSTALL_ROOT = + +# Directory from which applications will reference the library of Tcl scripts +# (note: you can set the TCL_LIBRARY environment variable at run-time to +# override this value): +TCL_LIBRARY = $(prefix)/lib/tcl$(VERSION) + +# Path to use at runtime to refer to LIB_INSTALL_DIR: +LIB_RUNTIME_DIR = $(libdir) + +# Directory in which to install the program tclsh: +BIN_INSTALL_DIR = $(INSTALL_ROOT)$(bindir) + +# Directory in which to install the .a or .so binary for the Tcl library: +LIB_INSTALL_DIR = $(INSTALL_ROOT)$(libdir) + +# Path name to use when installing library scripts. +SCRIPT_INSTALL_DIR = $(INSTALL_ROOT)$(TCL_LIBRARY) + +# Path name to use when installing Tcl modules. +MODULE_INSTALL_DIR = $(SCRIPT_INSTALL_DIR)/../tcl9 + +# Directory in which to install the include file tcl.h: +INCLUDE_INSTALL_DIR = $(INSTALL_ROOT)$(includedir) + +# Directory in which to (optionally) install the private tcl headers: +PRIVATE_INCLUDE_INSTALL_DIR = $(INSTALL_ROOT)$(includedir) + +# Top-level directory in which to install manual entries: +MAN_INSTALL_DIR = $(INSTALL_ROOT)$(mandir) + +# Directory in which to install manual entry for tclsh: +MAN1_INSTALL_DIR = $(MAN_INSTALL_DIR)/man1 + +# Directory in which to install manual entries for Tcl's C library procedures: +MAN3_INSTALL_DIR = $(MAN_INSTALL_DIR)/man3 + +# Directory in which to install manual entries for the built-in Tcl commands: +MANN_INSTALL_DIR = $(MAN_INSTALL_DIR)/mann + +# warning flags +CFLAGS_WARNING = @CFLAGS_WARNING@ + +# The default switches for optimization or debugging +CFLAGS_DEBUG = @CFLAGS_DEBUG@ +CFLAGS_OPTIMIZE = @CFLAGS_OPTIMIZE@ + +# To change the compiler switches, for example to change from optimization to +# debugging symbols, change the following line: +#CFLAGS = $(CFLAGS_DEBUG) +#CFLAGS = $(CFLAGS_OPTIMIZE) +#CFLAGS = $(CFLAGS_DEBUG) $(CFLAGS_OPTIMIZE) +CFLAGS = @CFLAGS@ @CFLAGS_DEFAULT@ -DMP_FIXED_CUTOFFS -D__USE_MINGW_ANSI_STDIO=0 + +# To compile without backward compatibility and deprecated code uncomment the +# following +NO_DEPRECATED_FLAGS = +#NO_DEPRECATED_FLAGS = -DTCL_NO_DEPRECATED + +# To enable compilation debugging reverse the comment characters on one of the +# following lines. +COMPILE_DEBUG_FLAGS = +#COMPILE_DEBUG_FLAGS = -DTCL_COMPILE_DEBUG +#COMPILE_DEBUG_FLAGS = -DTCL_COMPILE_DEBUG -DTCL_COMPILE_STATS + +SRC_DIR = @srcdir@ +ROOT_DIR = @srcdir@/.. +TOP_DIR = $(shell cd '@srcdir@/..'; pwd -W 2>/dev/null || pwd -P) +BUILD_DIR = @builddir@ +GENERIC_DIR = $(TOP_DIR)/generic +WIN_DIR = $(TOP_DIR)/win +UNIX_DIR = $(TOP_DIR)/unix +COMPAT_DIR = $(TOP_DIR)/compat +PKGS_DIR = $(TOP_DIR)/pkgs +TOOL_DIR = $(TOP_DIR)/tools +ZLIB_DIR = $(COMPAT_DIR)/zlib +MINIZIP_DIR = $(ZLIB_DIR)/contrib/minizip +TOMMATH_DIR = $(TOP_DIR)/libtommath + +# Converts a POSIX path to a Windows native path. +CYGPATH = @CYGPATH@ + +libdir_native = $(shell $(CYGPATH) '$(libdir)') +bindir_native = $(shell $(CYGPATH) '$(bindir)') +includedir_native = $(shell $(CYGPATH) '$(includedir)') +mandir_native = $(shell $(CYGPATH) '$(mandir)') +TCL_LIBRARY_NATIVE = $(shell $(CYGPATH) '$(TCL_LIBRARY)') +GENERIC_DIR_NATIVE = $(shell $(CYGPATH) '$(GENERIC_DIR)') +WIN_DIR_NATIVE = $(shell $(CYGPATH) '$(WIN_DIR)') +ROOT_DIR_NATIVE = $(shell $(CYGPATH) '$(ROOT_DIR)') +TOOL_DIR_NATIVE = $(shell $(CYGPATH) '$(TOOL_DIR)') +SCRIPT_INSTALL_DIR_NATIVE = $(shell $(CYGPATH) '$(SCRIPT_INSTALL_DIR)') +INCLUDE_INSTALL_DIR_NATIVE = $(shell $(CYGPATH) '$(INCLUDE_INSTALL_DIR)') +MAN_INSTALL_DIR_NATIVE = $(shell $(CYGPATH) '$(MAN_INSTALL_DIR)') +ROOT_DIR_WIN_NATIVE = $(shell cd '$(ROOT_DIR)' ; pwd -W 2>/dev/null || pwd -P) +ZLIB_DIR_NATIVE = $(shell $(CYGPATH) '$(ZLIB_DIR)') +MINIZIP_DIR_NATIVE = $(shell $(CYGPATH) '$(MINIZIP_DIR)') +TOMMATH_DIR_NATIVE = $(shell $(CYGPATH) '$(TOMMATH_DIR)') + +DLLSUFFIX = @DLLSUFFIX@ +LIBSUFFIX = @LIBSUFFIX@ +EXESUFFIX = @EXESUFFIX@ + +VER = @TCL_MAJOR_VERSION@@TCL_MINOR_VERSION@ +DOTVER = @TCL_MAJOR_VERSION@.@TCL_MINOR_VERSION@ +DDEVER = @TCL_DDE_MAJOR_VERSION@@TCL_DDE_MINOR_VERSION@ +DDEDOTVER = @TCL_DDE_MAJOR_VERSION@.@TCL_DDE_MINOR_VERSION@ +REGVER = @TCL_REG_MAJOR_VERSION@@TCL_REG_MINOR_VERSION@ +REGDOTVER = @TCL_REG_MAJOR_VERSION@.@TCL_REG_MINOR_VERSION@ + +TCL_ZIP_FILE = @TCL_ZIP_FILE@ +TCL_VFS_PATH = libtcl.vfs/tcl_library +TCL_VFS_ROOT = libtcl.vfs + + +TCL_STUB_LIB_FILE = @TCL_STUB_LIB_FILE@ +TCL_DLL_FILE = @TCL_DLL_FILE@ +TCL_LIB_FILE = @TCL_LIB_FILE@ +DDE_DLL_FILE = tcl9dde$(DDEVER)${DLLSUFFIX} +DDE_DLL_FILE8 = tcldde$(DDEVER)${DLLSUFFIX} +DDE_LIB_FILE = @LIBPREFIX@tcldde$(DDEVER)${DLLSUFFIX}${LIBSUFFIX} +REG_DLL_FILE = tcl9registry$(REGVER)${DLLSUFFIX} +REG_DLL_FILE8 = tclregistry$(REGVER)${DLLSUFFIX} +REG_LIB_FILE = @LIBPREFIX@tclregistry$(REGVER)${DLLSUFFIX}${LIBSUFFIX} +TEST_DLL_FILE = tcltest$(VER)${DLLSUFFIX} +TEST_EXE_FILE = tcltest${EXESUFFIX} +TEST_LIB_FILE = @LIBPREFIX@tcltest$(VER)${DLLSUFFIX}${LIBSUFFIX} +TEST_LOAD_PRMS = lappend ::auto_path {$(ROOT_DIR_WIN_NATIVE)/tests};\ + package ifneeded dde 1.4.6 [list load ${DDE_DLL_FILE}];\ + package ifneeded registry 1.3.7 [list load ${REG_DLL_FILE}] +TEST_LOAD_FACILITIES = package ifneeded tcl::test ${VERSION}@TCL_PATCH_LEVEL@ [list load ${TEST_DLL_FILE} Tcltest];\ + $(TEST_LOAD_PRMS) +ZLIB_DLL_FILE = zlib1.dll +TOMMATH_DLL_FILE = libtommath.dll + +SHARED_LIBRARIES = $(TCL_DLL_FILE) @ZLIB_DLL_FILE@ @TOMMATH_DLL_FILE@ +STATIC_LIBRARIES = $(TCL_LIB_FILE) + +TCLSH = tclsh$(VER)${EXESUFFIX} +WINE = @WINE@ +CAT32 = cat32$(EXEEXT) + +# For cross-compiled builds, TCL_EXE is the name of a tclsh executable that is +# available *BEFORE* running make for the first time. Certain build targets +# (make genstubs, make install) need it to be available on the PATH. This +# executable should *NOT* be required just to do a normal build although +# it can be required to run make dist. +TCL_EXE = @TCL_EXE@ + +@SET_MAKE@ + +# Setting the VPATH variable to a list of paths will cause the Makefile to +# look into these paths when resolving .c to .obj dependencies. + +VPATH = $(GENERIC_DIR):$(WIN_DIR):$(COMPAT_DIR):$(ZLIB_DIR):$(TOMMATH_DIR) + +AR = @AR@ +RANLIB = @RANLIB@ +CC = @CC@ +RC = @RC@ +RES = @RES@ +AC_FLAGS = @EXTRA_CFLAGS@ @DEFS@ +CPPFLAGS = @CPPFLAGS@ +LDFLAGS_DEBUG = @LDFLAGS_DEBUG@ +LDFLAGS_OPTIMIZE = @LDFLAGS_OPTIMIZE@ +LDFLAGS = @LDFLAGS@ @LDFLAGS_DEFAULT@ +LDFLAGS_CONSOLE = @LDFLAGS_CONSOLE@ +LDFLAGS_WINDOW = @LDFLAGS_WINDOW@ +EXEEXT = @EXEEXT@ +OBJEXT = @OBJEXT@ +STLIB_LD = @STLIB_LD@ +SHLIB_LD = @SHLIB_LD@ +SHLIB_LD_LIBS = @SHLIB_LD_LIBS@ +SHLIB_CFLAGS = @SHLIB_CFLAGS@ +SHLIB_SUFFIX = @SHLIB_SUFFIX@ +LIBS = @LIBS@ $(shell $(CYGPATH) '@ZLIB_LIBS@') $(shell $(CYGPATH) '@TOMMATH_LIBS@') + +RMDIR = rm -rf +MKDIR = mkdir -p +SHELL = @SHELL@ +RM = rm -f +COPY = cp +LN = ln +GDB = gdb +INSTALL = $(SHELL) $(UNIX_DIR)/install-sh -c +INSTALL_PROGRAM = ${INSTALL} +INSTALL_LIBRARY = ${INSTALL} +INSTALL_DATA = ${INSTALL} -m 644 +INSTALL_DATA_DIR = ${INSTALL} -d -m 755 + +### +# Tip 430 - ZipFS Modifications +### + +TCL_ZIP_FILE = @TCL_ZIP_FILE@ +TCL_VFS_PATH = libtcl.vfs/tcl_library +TCL_VFS_ROOT = libtcl.vfs + +HOST_CC = @CC_FOR_BUILD@ +HOST_EXEEXT = @EXEEXT_FOR_BUILD@ +HOST_OBJEXT = @OBJEXT_FOR_BUILD@ +ZIPFS_BUILD = @ZIPFS_BUILD@ +NATIVE_ZIP = @ZIP_PROG@ +ZIP_PROG_OPTIONS = @ZIP_PROG_OPTIONS@ +ZIP_PROG_VFSSEARCH = @ZIP_PROG_VFSSEARCH@ +SHARED_BUILD = @SHARED_BUILD@ +INSTALL_MSGS = @INSTALL_MSGS@ +INSTALL_LIBRARIES = @INSTALL_LIBRARIES@ + +# Fully qualify library path so that `make test` +# does not depend on the current directory. +# Only define these if not embedding the library +ifeq ($(ZIPFS_BUILD), 0) +LIBRARY_DIR1 = $(shell cd '$(ROOT_DIR_NATIVE)/library' ; pwd -P) +LIBRARY_DIR = $(shell $(CYGPATH) '$(LIBRARY_DIR1)') +endif + +# Minizip +MINIZIP_OBJS = \ + adler32.$(HOST_OBJEXT) \ + compress.$(HOST_OBJEXT) \ + crc32.$(HOST_OBJEXT) \ + deflate.$(HOST_OBJEXT) \ + infback.$(HOST_OBJEXT) \ + inffast.$(HOST_OBJEXT) \ + inflate.$(HOST_OBJEXT) \ + inftrees.$(HOST_OBJEXT) \ + ioapi.$(HOST_OBJEXT) \ + iowin32.$(HOST_OBJEXT) \ + trees.$(HOST_OBJEXT) \ + uncompr.$(HOST_OBJEXT) \ + zip.$(HOST_OBJEXT) \ + zutil.$(HOST_OBJEXT) \ + minizip.$(HOST_OBJEXT) + +ZIP_INSTALL_OBJS = @ZIP_INSTALL_OBJS@ + +CC_SWITCHES = -I"${BUILD_DIR}" -I"${GENERIC_DIR_NATIVE}" -I"${TOMMATH_DIR_NATIVE}" \ +-I"${ZLIB_DIR_NATIVE}" -I"${WIN_DIR_NATIVE}" \ +${CFLAGS} ${CFLAGS_WARNING} ${SHLIB_CFLAGS} -DMP_PREC=4 \ +${AC_FLAGS} ${COMPILE_DEBUG_FLAGS} ${NO_DEPRECATED_FLAGS} + +CC_OBJNAME = @CC_OBJNAME@ +CC_EXENAME = @CC_EXENAME@ + +STUB_CC_SWITCHES = -I"${GENERIC_DIR_NATIVE}" -I"${TOMMATH_DIR_NATIVE}" \ +-I"${ZLIB_DIR_NATIVE}" -I"${WIN_DIR_NATIVE}" \ +${CFLAGS} ${CFLAGS_WARNING} ${SHLIB_CFLAGS} -DMP_PREC=4 \ +${AC_FLAGS} ${COMPILE_DEBUG_FLAGS} + +TCLTEST_OBJS = \ + tclTest.$(OBJEXT) \ + tclTestABSList.$(OBJEXT) \ + tclTestObj.$(OBJEXT) \ + tclTestProcBodyObj.$(OBJEXT) \ + tclMutexTest.$(OBJEXT) \ + tclThreadTest.$(OBJEXT) \ + tclWinTest.$(OBJEXT) + +GENERIC_OBJS = \ + regcomp.$(OBJEXT) \ + regexec.$(OBJEXT) \ + regfree.$(OBJEXT) \ + regerror.$(OBJEXT) \ + tclAlloc.$(OBJEXT) \ + tclArithSeries.$(OBJEXT) \ + tclAssembly.$(OBJEXT) \ + tclAsync.$(OBJEXT) \ + tclBasic.$(OBJEXT) \ + tclBinary.$(OBJEXT) \ + tclCkalloc.$(OBJEXT) \ + tclClock.$(OBJEXT) \ + tclClockFmt.$(OBJEXT) \ + tclCmdAH.$(OBJEXT) \ + tclCmdIL.$(OBJEXT) \ + tclCmdMZ.$(OBJEXT) \ + tclCompCmds.$(OBJEXT) \ + tclCompCmdsGR.$(OBJEXT) \ + tclCompCmdsSZ.$(OBJEXT) \ + tclCompExpr.$(OBJEXT) \ + tclCompile.$(OBJEXT) \ + tclConfig.$(OBJEXT) \ + tclDate.$(OBJEXT) \ + tclDictObj.$(OBJEXT) \ + tclDisassemble.$(OBJEXT) \ + tclEncoding.$(OBJEXT) \ + tclEnsemble.$(OBJEXT) \ + tclEnv.$(OBJEXT) \ + tclEvent.$(OBJEXT) \ + tclExecute.$(OBJEXT) \ + tclFCmd.$(OBJEXT) \ + tclFileName.$(OBJEXT) \ + tclGet.$(OBJEXT) \ + tclHash.$(OBJEXT) \ + tclHistory.$(OBJEXT) \ + tclIcu.$(OBJEXT) \ + tclIndexObj.$(OBJEXT) \ + tclInterp.$(OBJEXT) \ + tclIO.$(OBJEXT) \ + tclIOCmd.$(OBJEXT) \ + tclIOGT.$(OBJEXT) \ + tclIORChan.$(OBJEXT) \ + tclIORTrans.$(OBJEXT) \ + tclIOSock.$(OBJEXT) \ + tclIOUtil.$(OBJEXT) \ + tclLink.$(OBJEXT) \ + tclLiteral.$(OBJEXT) \ + tclListObj.$(OBJEXT) \ + tclLoad.$(OBJEXT) \ + tclMainW.$(OBJEXT) \ + tclMain.$(OBJEXT) \ + tclNamesp.$(OBJEXT) \ + tclNotify.$(OBJEXT) \ + tclOO.$(OBJEXT) \ + tclOOBasic.$(OBJEXT) \ + tclOOCall.$(OBJEXT) \ + tclOODefineCmds.$(OBJEXT) \ + tclOOInfo.$(OBJEXT) \ + tclOOMethod.$(OBJEXT) \ + tclOOProp.$(OBJEXT) \ + tclOOStubInit.$(OBJEXT) \ + tclObj.$(OBJEXT) \ + tclOptimize.$(OBJEXT) \ + tclPanic.$(OBJEXT) \ + tclParse.$(OBJEXT) \ + tclPathObj.$(OBJEXT) \ + tclPipe.$(OBJEXT) \ + tclPkg.$(OBJEXT) \ + tclPkgConfig.$(OBJEXT) \ + tclPosixStr.$(OBJEXT) \ + tclPreserve.$(OBJEXT) \ + tclProc.$(OBJEXT) \ + tclProcess.$(OBJEXT) \ + tclRegexp.$(OBJEXT) \ + tclResolve.$(OBJEXT) \ + tclResult.$(OBJEXT) \ + tclScan.$(OBJEXT) \ + tclStringObj.$(OBJEXT) \ + tclStrIdxTree.$(OBJEXT) \ + tclStrToD.$(OBJEXT) \ + tclStubInit.$(OBJEXT) \ + tclThread.$(OBJEXT) \ + tclThreadAlloc.$(OBJEXT) \ + tclThreadJoin.$(OBJEXT) \ + tclThreadStorage.$(OBJEXT) \ + tclTimer.$(OBJEXT) \ + tclTomMathInterface.$(OBJEXT) \ + tclTrace.$(OBJEXT) \ + tclUtf.$(OBJEXT) \ + tclUtil.$(OBJEXT) \ + tclVar.$(OBJEXT) \ + tclZipfs.$(OBJEXT) \ + tclZlib.$(OBJEXT) + +TOMMATH_OBJS = \ + bn_mp_add.${OBJEXT} \ + bn_mp_add_d.${OBJEXT} \ + bn_mp_and.${OBJEXT} \ + bn_mp_clamp.${OBJEXT} \ + bn_mp_clear.${OBJEXT} \ + bn_mp_clear_multi.${OBJEXT} \ + bn_mp_cmp.${OBJEXT} \ + bn_mp_cmp_d.${OBJEXT} \ + bn_mp_cmp_mag.${OBJEXT} \ + bn_mp_cnt_lsb.${OBJEXT} \ + bn_mp_copy.${OBJEXT} \ + bn_mp_count_bits.${OBJEXT} \ + bn_mp_div.${OBJEXT} \ + bn_mp_div_d.${OBJEXT} \ + bn_mp_div_2.${OBJEXT} \ + bn_mp_div_2d.${OBJEXT} \ + bn_s_mp_div_3.${OBJEXT} \ + bn_mp_exch.${OBJEXT} \ + bn_mp_expt_n.${OBJEXT} \ + bn_mp_get_mag_u64.${OBJEXT} \ + bn_mp_grow.${OBJEXT} \ + bn_mp_init.${OBJEXT} \ + bn_mp_init_copy.${OBJEXT} \ + bn_mp_init_i64.${OBJEXT} \ + bn_mp_init_multi.${OBJEXT} \ + bn_mp_init_set.${OBJEXT} \ + bn_mp_init_size.${OBJEXT} \ + bn_mp_init_u64.${OBJEXT} \ + bn_mp_lshd.${OBJEXT} \ + bn_mp_mod.${OBJEXT} \ + bn_mp_mod_2d.${OBJEXT} \ + bn_mp_mul.${OBJEXT} \ + bn_mp_mul_2.${OBJEXT} \ + bn_mp_mul_2d.${OBJEXT} \ + bn_mp_mul_d.${OBJEXT} \ + bn_mp_neg.${OBJEXT} \ + bn_mp_or.${OBJEXT} \ + bn_mp_pack.${OBJEXT} \ + bn_mp_pack_count.${OBJEXT} \ + bn_mp_radix_size.${OBJEXT} \ + bn_mp_radix_smap.${OBJEXT} \ + bn_mp_read_radix.${OBJEXT} \ + bn_mp_rshd.${OBJEXT} \ + bn_mp_set_i64.${OBJEXT} \ + bn_mp_set_u64.${OBJEXT} \ + bn_mp_shrink.${OBJEXT} \ + bn_mp_sqr.${OBJEXT} \ + bn_mp_sqrt.${OBJEXT} \ + bn_mp_sub.${OBJEXT} \ + bn_mp_sub_d.${OBJEXT} \ + bn_mp_signed_rsh.${OBJEXT} \ + bn_mp_to_ubin.${OBJEXT} \ + bn_mp_to_radix.${OBJEXT} \ + bn_mp_ubin_size.${OBJEXT} \ + bn_mp_unpack.${OBJEXT} \ + bn_mp_xor.${OBJEXT} \ + bn_mp_zero.${OBJEXT} \ + bn_s_mp_add.${OBJEXT} \ + bn_s_mp_balance_mul.$(OBJEXT) \ + bn_s_mp_karatsuba_mul.${OBJEXT} \ + bn_s_mp_karatsuba_sqr.$(OBJEXT) \ + bn_s_mp_mul_digs.${OBJEXT} \ + bn_s_mp_mul_digs_fast.${OBJEXT} \ + bn_s_mp_reverse.${OBJEXT} \ + bn_s_mp_sqr_fast.${OBJEXT} \ + bn_s_mp_sqr.${OBJEXT} \ + bn_s_mp_sub.${OBJEXT} \ + bn_s_mp_toom_mul.${OBJEXT} \ + bn_s_mp_toom_sqr.${OBJEXT} + + +WIN_OBJS = \ + tclWin32Dll.$(OBJEXT) \ + tclWinChan.$(OBJEXT) \ + tclWinConsole.$(OBJEXT) \ + tclWinSerial.$(OBJEXT) \ + tclWinError.$(OBJEXT) \ + tclWinFCmd.$(OBJEXT) \ + tclWinFile.$(OBJEXT) \ + tclWinInit.$(OBJEXT) \ + tclWinLoad.$(OBJEXT) \ + tclWinNotify.$(OBJEXT) \ + tclWinPipe.$(OBJEXT) \ + tclWinSock.$(OBJEXT) \ + tclWinThrd.$(OBJEXT) \ + tclWinTime.$(OBJEXT) + +DDE_OBJS = tclWinDde.$(OBJEXT) + +REG_OBJS = tclWinReg.$(OBJEXT) + +STUB_OBJS = \ + tclStubLib.$(OBJEXT) \ + tclStubCall.$(OBJEXT) \ + tclStubLibTbl.$(OBJEXT) \ + tclTomMathStubLib.$(OBJEXT) \ + tclOOStubLib.$(OBJEXT) \ + tclWinPanic.$(OBJEXT) + +TCLSH_OBJS = tclAppInit.$(OBJEXT) + +ZLIB_OBJS = \ + adler32.$(OBJEXT) \ + compress.$(OBJEXT) \ + crc32.$(OBJEXT) \ + deflate.$(OBJEXT) \ + infback.$(OBJEXT) \ + inffast.$(OBJEXT) \ + inflate.$(OBJEXT) \ + inftrees.$(OBJEXT) \ + trees.$(OBJEXT) \ + uncompr.$(OBJEXT) \ + zutil.$(OBJEXT) + +TCL_OBJS = ${GENERIC_OBJS} ${WIN_OBJS} @ZLIB_OBJS@ @TOMMATH_OBJS@ + +TCL_DOCS = "$(ROOT_DIR_NATIVE)"/doc/*.[13n] + +all: binaries libraries doc packages + +# Test-suite helper (can be used to test Tcl from build directory with all expected modules). +# To start from windows shell use: +# > tcltest.cmd -verbose bps -file fileName.test +# or from mingw/msys shell: +# $ ./tcltest -verbose bps -file fileName.test + +tcltest.cmd: Makefile + @echo 'Create tcltest.cmd helpers'; + @(\ + echo '@echo off'; \ + echo 'rem set LANG=en_US'; \ + echo 'set BDP=%~dp0'; \ + echo 'set OWD=%CD%'; \ + echo 'cd /d %TEMP%'; \ + echo 'rem "%BDP%\$(TCLSH)" "$(ROOT_DIR_WIN_NATIVE)/tests/all.tcl" %TESTFLAGS% -load "$(TEST_LOAD_FACILITIES)" %*'; \ + echo '"%BDP%\$(TEST_EXE_FILE)" "$(ROOT_DIR_WIN_NATIVE)/tests/all.tcl" %TESTFLAGS% -load "$(TEST_LOAD_PRMS)" %*'; \ + echo 'cd /d %OWD%'; \ + ) > tcltest.cmd; + @(\ + echo '#!/bin/sh'; \ + echo '#LANG=en_US'; \ + echo 'BDP=$$(dirname $$(readlink -f %0))'; \ + echo 'cd /tmp'; \ + echo '#"$$BDP/$(TCLSH)" "$(ROOT_DIR_WIN_NATIVE)/tests/all.tcl" $$TESTFLAGS -load "$(TEST_LOAD_FACILITIES)" "$$@"'; \ + echo '"$$BDP/$(TEST_EXE_FILE)" "$(ROOT_DIR_WIN_NATIVE)/tests/all.tcl" $$TESTFLAGS -load "$(TEST_LOAD_PRMS)" "$$@"'; \ + ) > tcltest.sh; + +tcltest.sh: tcltest.cmd + +tcltest: binaries $(TEST_EXE_FILE) $(TEST_DLL_FILE) $(CAT32) tcltest.cmd + +binaries: $(TCL_STUB_LIB_FILE) @LIBRARIES@ winextensions ${TCL_ZIP_FILE} $(TCLSH) + +winextensions: ${DDE_DLL_FILE} ${REG_DLL_FILE} ${DDE_DLL_FILE8} ${REG_DLL_FILE8} + +libraries: + +doc: + +tclzipfile: ${TCL_ZIP_FILE} + +${TCL_ZIP_FILE}: ${ZIP_INSTALL_OBJS} ${DDE_DLL_FILE} ${REG_DLL_FILE} + @rm -rf ${TCL_VFS_ROOT} + @mkdir -p ${TCL_VFS_PATH} + @echo "creating ${TCL_VFS_PATH} (prepare compression)" + @( \ + $(COPY) -a $(TOP_DIR)/library/* ${TCL_VFS_PATH}; \ + $(COPY) -a ${TCL_VFS_PATH}/manifest.txt ${TCL_VFS_PATH}/pkgIndex.tcl; \ + rm -rf ${TCL_VFS_PATH}/dde; \ + rm -rf ${TCL_VFS_PATH}/registry; \ + ) + (zip=`(realpath '${NATIVE_ZIP}' || readlink -m '${NATIVE_ZIP}') 2>/dev/null || \ + (echo '${NATIVE_ZIP}' | sed "s?^\./?$$(pwd)/?")`; \ + cd '${TCL_VFS_ROOT}' && \ + $$zip ${ZIP_PROG_OPTIONS} ../${TCL_ZIP_FILE} ${ZIP_PROG_VFSSEARCH} >/dev/null && \ + echo "${TCL_ZIP_FILE} successful created with $$zip" && \ + cd ..) + +$(TCLSH): $(TCLSH_OBJS) @LIBRARIES@ $(TCL_STUB_LIB_FILE) tclsh.$(RES) ${TCL_ZIP_FILE} + $(CC) $(CFLAGS) $(TCLSH_OBJS) $(TCL_LIB_FILE) $(TCL_STUB_LIB_FILE) $(LIBS) \ + tclsh.$(RES) $(CC_EXENAME) $(LDFLAGS_CONSOLE) + $(COPY) tclsh.exe.manifest $(TCLSH).manifest + @VC_MANIFEST_EMBED_EXE@ + @if test "${ZIPFS_BUILD}" = "2" ; then \ + cat ${TCL_ZIP_FILE} >> ${TCLSH}; \ + fi + +cat32.$(OBJEXT): cat.c + $(CC) -c $(CC_SWITCHES) -DUNICODE -D_UNICODE @DEPARG@ $(CC_OBJNAME) + +$(CAT32): cat32.$(OBJEXT) + $(CC) $(CFLAGS) cat32.$(OBJEXT) $(CC_EXENAME) $(LIBS) $(LDFLAGS_CONSOLE) + +# The following targets are configured by autoconf to generate either a shared +# library or static library + +${TCL_STUB_LIB_FILE}: ${STUB_OBJS} ${DDE_OBJS} ${REG_OBJS} + @$(RM) ${TCL_STUB_LIB_FILE} + @MAKE_STUB_LIB@ ${STUB_OBJS} ${DDE_OBJS} ${REG_OBJS} + @POST_MAKE_LIB@ + +${TCL_DLL_FILE}: ${TCL_LIB_FILE} ${TCL_OBJS} tcl.$(RES) @ZLIB_DLL_FILE@ @TOMMATH_DLL_FILE@ ${TCL_ZIP_FILE} + @$(RM) ${TCL_DLL_FILE} $(TCL_LIB_FILE) + @MAKE_DLL@ ${TCL_OBJS} tcl.$(RES) $(SHLIB_LD_LIBS) + $(COPY) tclsh.exe.manifest ${TCL_DLL_FILE}.manifest + @VC_MANIFEST_EMBED_DLL@ + @if test "${ZIPFS_BUILD}" = "1" ; then \ + cat ${TCL_ZIP_FILE} >> ${TCL_DLL_FILE}; \ + fi + +ifeq (,$(findstring --disable-shared,$(PKG_CFG_ARGS))) +${TCL_LIB_FILE}: + @$(RM) ${TCL_DLL_FILE} $(TCL_LIB_FILE) +else +${TCL_LIB_FILE}: ${TCL_OBJS} tclWinPanic.$(OBJEXT) ${DDE_OBJS} ${REG_OBJS} + @$(RM) ${TCL_LIB_FILE} + @MAKE_LIB@ ${TCL_OBJS} tclWinPanic.$(OBJEXT) ${DDE_OBJS} ${REG_OBJS} + @POST_MAKE_LIB@ +endif + +${DDE_DLL_FILE}: ${TCL_STUB_LIB_FILE} ${DDE_OBJS} + @MAKE_DLL@ ${DDE_OBJS} $(TCL_STUB_LIB_FILE) $(SHLIB_LD_LIBS) + $(COPY) tclsh.exe.manifest ${DDE_DLL_FILE}.manifest + +${REG_DLL_FILE}: ${TCL_STUB_LIB_FILE} ${REG_OBJS} + @MAKE_DLL@ ${REG_OBJS} $(TCL_STUB_LIB_FILE) $(SHLIB_LD_LIBS) + $(COPY) tclsh.exe.manifest ${REG_DLL_FILE}.manifest + +${DDE_DLL_FILE8}: ${TCL_STUB_LIB_FILE} tcl8WinDde.$(OBJEXT) + @MAKE_DLL@ tcl8WinDde.$(OBJEXT) $(TCL_STUB_LIB_FILE) $(SHLIB_LD_LIBS) + $(COPY) tclsh.exe.manifest ${DDE_DLL_FILE8}.manifest + +${REG_DLL_FILE8}: ${TCL_STUB_LIB_FILE} tcl8WinReg.$(OBJEXT) + @MAKE_DLL@ -DTCL_MAJOR_VERSION=8 tcl8WinReg.$(OBJEXT) $(TCL_STUB_LIB_FILE) $(SHLIB_LD_LIBS) + $(COPY) tclsh.exe.manifest ${REG_DLL_FILE8}.manifest + +${TEST_DLL_FILE}: ${TCL_STUB_LIB_FILE} ${TCLTEST_OBJS} + @$(RM) ${TEST_DLL_FILE} ${TEST_LIB_FILE} + @MAKE_DLL@ ${TCLTEST_OBJS} $(TCL_STUB_LIB_FILE) $(SHLIB_LD_LIBS) + $(COPY) tclsh.exe.manifest ${TEST_DLL_FILE}.manifest + +${TEST_EXE_FILE}: @LIBRARIES@ ${TCL_STUB_LIB_FILE} ${TCLTEST_OBJS} tclTestMain.${OBJEXT} + @$(RM) ${TEST_EXE_FILE} + $(CC) $(CFLAGS) $(TCLTEST_OBJS) tclTestMain.$(OBJEXT) $(TCL_LIB_FILE) $(TCL_STUB_LIB_FILE) $(LIBS) \ + tclsh.$(RES) $(CC_EXENAME) $(LDFLAGS_CONSOLE) + $(COPY) tclsh.exe.manifest ${TEST_EXE_FILE}.manifest + +# use prebuilt zlib1.dll +${ZLIB_DLL_FILE}: ${TCL_STUB_LIB_FILE} + @if test "@ZLIB_LIBS@set" = "${ZLIB_DIR_NATIVE}/win64-arm/zdll.libset" ; then \ + $(COPY) $(ZLIB_DIR)/win64-arm/${ZLIB_DLL_FILE} ${ZLIB_DLL_FILE}; \ + elif test "@ZLIB_LIBS@set" = "${ZLIB_DIR_NATIVE}/win64-arm/libz.dll.aset" ; then \ + $(COPY) $(ZLIB_DIR)/win64-arm/${ZLIB_DLL_FILE} ${ZLIB_DLL_FILE}; \ + elif test "@ZLIB_LIBS@set" = "${ZLIB_DIR_NATIVE}/win32/zdll.libset" ; then \ + $(COPY) $(ZLIB_DIR)/win32/${ZLIB_DLL_FILE} ${ZLIB_DLL_FILE}; \ + else \ + $(COPY) $(ZLIB_DIR)/win64/${ZLIB_DLL_FILE} ${ZLIB_DLL_FILE}; \ + fi; + +# use pre-built libtommath.dll +${TOMMATH_DLL_FILE}: ${TCL_STUB_LIB_FILE} + @if test "@TOMMATH_LIBS@set" = "${TOMMATH_DIR_NATIVE}/win64-arm/tommath.libset" ; then \ + $(COPY) $(TOMMATH_DIR)/win64-arm/${TOMMATH_DLL_FILE} ${TOMMATH_DLL_FILE}; \ + elif test "@TOMMATH_LIBS@set" = "${TOMMATH_DIR_NATIVE}/win64-arm/libtommath.dll.aset" ; then \ + $(COPY) $(TOMMATH_DIR)/win64-arm/${TOMMATH_DLL_FILE} ${TOMMATH_DLL_FILE}; \ + elif test "@TOMMATH_LIBS@set" = "${TOMMATH_DIR_NATIVE}/win32/tommath.libset" ; then \ + $(COPY) $(TOMMATH_DIR)/win32/${TOMMATH_DLL_FILE} ${TOMMATH_DLL_FILE}; \ + else \ + $(COPY) $(TOMMATH_DIR)/win64/${TOMMATH_DLL_FILE} ${TOMMATH_DLL_FILE}; \ + fi; + +# Add the object extension to the implicit rules. By default .obj is not +# automatically added. + +.SUFFIXES: .${OBJEXT} +.SUFFIXES: .$(RES) +.SUFFIXES: .rc + +# Special case object targets + +tclTestMain.${OBJEXT}: tclAppInit.c + $(CC) -c $(CC_SWITCHES) -DTCL_TEST -DUNICODE -D_UNICODE $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) + +tclWinInit.${OBJEXT}: tclWinInit.c + $(CC) -c $(CC_SWITCHES) -DBUILD_tcl $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) + +tclWinPipe.${OBJEXT}: tclWinPipe.c + $(CC) -c $(CC_SWITCHES) -DBUILD_tcl $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) + +tclWinReg.${OBJEXT}: tclWinReg.c + $(CC) -c $(CC_SWITCHES) $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) + +tcl8WinReg.${OBJEXT}: tclWinReg.c + $(CC) -o $@ -c $(CC_SWITCHES) -DTCL_MAJOR_VERSION=8 $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) + +tclWinDde.${OBJEXT}: tclWinDde.c + $(CC) -c $(CC_SWITCHES) $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) + +tcl8WinDde.${OBJEXT}: tclWinDde.c + $(CC) -o $@ -c $(CC_SWITCHES) -DTCL_MAJOR_VERSION=8 $(EXTFLAGS) @DEPARG@ $(CC_OBJNAME) + +tclAppInit.${OBJEXT}: tclAppInit.c + $(CC) -c $(CC_SWITCHES) $(EXTFLAGS) -DUNICODE -D_UNICODE @DEPARG@ $(CC_OBJNAME) + +tclMainW.${OBJEXT}: tclMain.c + $(CC) -c $(CC_SWITCHES) -DBUILD_tcl -DUNICODE -D_UNICODE @DEPARG@ $(CC_OBJNAME) + +# TIP #430, ZipFS Support +tclZipfs.${OBJEXT}: $(GENERIC_DIR)/tclZipfs.c + $(CC) -c $(CC_SWITCHES) -DBUILD_tcl \ + $(ZLIB_INCLUDE) -I$(MINIZIP_DIR_NATIVE) @DEPARG@ $(CC_OBJNAME) + + +# TIP #59, embedding of configuration information into the binary library. +# +# Part of Tcl's configuration information are the paths where it was installed +# and where it will look for its libraries (which can be different). We derive +# this information from the variables which can be overridden by the user. As +# every path can be configured separately we do not remember one general +# prefix/exec_prefix but all the different paths individually. + +tclPkgConfig.${OBJEXT}: tclPkgConfig.c + $(CC) -c $(CC_SWITCHES) \ + -DCFG_INSTALL_LIBDIR="\"$(LIB_INSTALL_DIR_NATIVE)\"" \ + -DCFG_INSTALL_BINDIR="\"$(BIN_INSTALL_DIR_NATIVE)\"" \ + -DCFG_INSTALL_SCRDIR="\"$(SCRIPT_INSTALL_DIR_NATIVE)\"" \ + -DCFG_INSTALL_INCDIR="\"$(INCLUDE_INSTALL_DIR_NATIVE)\"" \ + -DCFG_INSTALL_DOCDIR="\"$(MAN_INSTALL_DIR_NATIVE)\"" \ + \ + -DCFG_RUNTIME_LIBDIR="\"$(libdir_native)\"" \ + -DCFG_RUNTIME_BINDIR="\"$(bindir_native)\"" \ + -DCFG_RUNTIME_SCRDIR="\"$(TCL_LIBRARY_NATIVE)\"" \ + -DCFG_RUNTIME_INCDIR="\"$(includedir_native)\"" \ + -DCFG_RUNTIME_DOCDIR="\"$(mandir_native)\"" \ + -DCFG_RUNTIME_DLLFILE="\"$(TCL_DLL_FILE)\"" \ + -DBUILD_tcl \ + @DEPARG@ $(CC_OBJNAME) + +# tclDate.h dependencies: +tclClock.${OBJEXT}: tclClock.c tclDate.h +tclClockFmt.${OBJEXT}: tclClockFmt.c tclDate.h +tclDate.${OBJEXT}: tclDate.c tclDate.h + +tclEvent.${OBJEXT}: tclEvent.c tclUuid.h + +tclTest.${OBJEXT}: tclTest.c tclUuid.h + +$(TOP_DIR)/manifest.uuid: + printf "git-" >$(TOP_DIR)/manifest.uuid + (cd '$(TOP_DIR)'; git rev-parse HEAD >>$(TOP_DIR)/manifest.uuid || \ + (printf "svn-r" >$(TOP_DIR)/manifest.uuid ; \ + svn info --show-item last-changed-revision >>$(TOP_DIR)/manifest.uuid) || \ + printf "unknown" >$(TOP_DIR)/manifest.uuid) + +tclUuid.h: $(TOP_DIR)/manifest.uuid + echo "#define TCL_VERSION_UUID \\" >$@ + cat $(TOP_DIR)/manifest.uuid >>$@ + echo "" >>$@ + +# The following objects are part of the stub library and should not be built +# as DLL objects but none of the symbols should be exported + +tclStubLib.${OBJEXT}: tclStubLib.c + $(CC) -c $(CC_SWITCHES) -DSTATIC_BUILD @CFLAGS_NOLTO@ @DEPARG@ $(CC_OBJNAME) + +tclStubCall.${OBJEXT}: tclStubCall.c + $(CC) -c $(CC_SWITCHES) -DSTATIC_BUILD \ + -DCFG_RUNTIME_DLLFILE="\"$(TCL_DLL_FILE)\"" \ + -DCFG_RUNTIME_BINDIR="\"$(bindir_native)\"" \ + @DEPARG@ $(CC_OBJNAME) + +tclStubLibTbl.${OBJEXT}: tclStubLibTbl.c + $(CC) -c $(CC_SWITCHES) -DSTATIC_BUILD @DEPARG@ $(CC_OBJNAME) + +tclTomMathStubLib.${OBJEXT}: tclTomMathStubLib.c + $(CC) -c $(CC_SWITCHES) @CFLAGS_NOLTO@ @DEPARG@ $(CC_OBJNAME) + +tclOOStubLib.${OBJEXT}: tclOOStubLib.c + $(CC) -c $(CC_SWITCHES) @CFLAGS_NOLTO@ @DEPARG@ $(CC_OBJNAME) + +tclWinPanic.${OBJEXT}: tclWinPanic.c + $(CC) -c $(CC_SWITCHES) -DSTATIC_BUILD @DEPARG@ $(CC_OBJNAME) + +# Implicit rule for all object files that will end up in the Tcl library + +%.${OBJEXT}: %.c + $(CC) -c $(CC_SWITCHES) -DBUILD_tcl @DEPARG@ $(CC_OBJNAME) + +.rc.$(RES): + $(RC) @RC_OUT@ $@ @RC_TYPE@ @RC_DEFINES@ @RC_INCLUDE@ "$(GENERIC_DIR_NATIVE)" @RC_INCLUDE@ "$(WIN_DIR_NATIVE)" @DEPARG@ + +#-------------------------------------------------------------------------- +# Minizip implementation +#-------------------------------------------------------------------------- +adler32.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/adler32.c + +compress.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/compress.c + +crc32.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/crc32.c + +deflate.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/deflate.c + +ioapi.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -DMINIZIP_FOPEN_NO_64=1 -I$(ZLIB_DIR) -I$(ZLIB_DIR)/contrib/minizip -c \ + $(ZLIB_DIR)/contrib/minizip/ioapi.c + +iowin32.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -I$(ZLIB_DIR)/contrib/minizip -c $(ZLIB_DIR)/contrib/minizip/iowin32.c + +infback.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/infback.c + +inffast.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/inffast.c + +inflate.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/inflate.c + +inftrees.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/inftrees.c + +trees.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/trees.c + +uncompr.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/uncompr.c + +zip.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -I$(ZLIB_DIR)/contrib/minizip -c $(ZLIB_DIR)/contrib/minizip/zip.c + +zutil.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -I$(ZLIB_DIR) -c $(ZLIB_DIR)/zutil.c + +minizip.$(HOST_OBJEXT): + $(HOST_CC) -o $@ -DMINIZIP_FOPEN_NO_64=1 -DHAVE_DIRENT_H=1 -I$(ZLIB_DIR) -I$(ZLIB_DIR)/contrib/minizip -c \ + $(ZLIB_DIR)/contrib/minizip/minizip.c + +minizip${HOST_EXEEXT}: $(MINIZIP_OBJS) + $(HOST_CC) -o $@ $(MINIZIP_OBJS) + +# The following target generates the file generic/tclDate.c from the yacc +# grammar found in generic/tclGetDate.y. This is only run by hand as yacc is +# not available in all environments. The name of the .c file is different than +# the name of the .y file so that make doesn't try to automatically regenerate +# the .c file. + +gendate: + bison --output-file=$(GENERIC_DIR)/tclDate.c \ + --name-prefix=TclDate \ + --no-lines \ + $(GENERIC_DIR)/tclGetDate.y + +INSTALL_BASE_TARGETS = install-binaries $(INSTALL_LIBRARIES) $(INSTALL_MSGS) $(INSTALL_TZDATA) +INSTALL_DOC_TARGETS = install-doc +INSTALL_PACKAGE_TARGETS = install-packages +INSTALL_DEV_TARGETS = install-headers +INSTALL_EXTRA_TARGETS = +INSTALL_TARGETS = $(INSTALL_BASE_TARGETS) $(INSTALL_DOC_TARGETS) $(INSTALL_DEV_TARGETS) \ + $(INSTALL_PACKAGE_TARGETS) $(INSTALL_EXTRA_TARGETS) + +install: $(INSTALL_TARGETS) + +install-binaries: binaries + @for i in "$(LIB_INSTALL_DIR)" "$(BIN_INSTALL_DIR)"; \ + do \ + if [ ! -d "$$i" ] ; then \ + echo "Making directory $$i"; \ + $(MKDIR) "$$i"; \ + chmod 755 "$$i"; \ + else true; \ + fi; \ + done; + @for i in dde${DDEDOTVER} registry${REGDOTVER}; \ + do \ + if [ ! -d "$(LIB_INSTALL_DIR)/$$i" ] ; then \ + echo "Making directory $(LIB_INSTALL_DIR)/$$i"; \ + $(MKDIR) "$(LIB_INSTALL_DIR)/$$i"; \ + else true; \ + fi; \ + done; + @for i in $(TCL_DLL_FILE) $(ZLIB_DLL_FILE) $(TOMMATH_DLL_FILE) $(TCLSH); \ + do \ + if [ -f $$i ]; then \ + echo "Installing $$i to $(BIN_INSTALL_DIR)/"; \ + $(COPY) $$i "$(BIN_INSTALL_DIR)"; \ + fi; \ + done + @for i in tclConfig.sh tclooConfig.sh $(TCL_LIB_FILE) $(TCL_STUB_LIB_FILE) @ZLIB_LIBS@ @TOMMATH_LIBS@; \ + do \ + if [ -f $$i ]; then \ + echo "Installing $$i to $(LIB_INSTALL_DIR)/"; \ + $(COPY) $$i "$(LIB_INSTALL_DIR)"; \ + fi; \ + done + @if [ -f $(DDE_DLL_FILE) ]; then \ + echo Installing $(DDE_DLL_FILE); \ + $(COPY) $(DDE_DLL_FILE) "$(LIB_INSTALL_DIR)/dde${DDEDOTVER}"; \ + $(COPY) $(ROOT_DIR)/library/dde/pkgIndex.tcl \ + "$(LIB_INSTALL_DIR)/dde${DDEDOTVER}"; \ + fi + @if [ -f $(DDE_DLL_FILE8) ]; then \ + echo Installing $(DDE_DLL_FILE8); \ + $(COPY) $(DDE_DLL_FILE8) "$(LIB_INSTALL_DIR)/dde${DDEDOTVER}"; \ + fi + @if [ -f $(DDE_LIB_FILE) ]; then \ + echo Installing $(DDE_LIB_FILE); \ + $(COPY) $(DDE_LIB_FILE) "$(LIB_INSTALL_DIR)/dde${DDEDOTVER}"; \ + fi + @if [ -f $(REG_DLL_FILE) ]; then \ + echo Installing $(REG_DLL_FILE); \ + $(COPY) $(REG_DLL_FILE) "$(LIB_INSTALL_DIR)/registry${REGDOTVER}"; \ + $(COPY) $(ROOT_DIR)/library/registry/pkgIndex.tcl \ + "$(LIB_INSTALL_DIR)/registry${REGDOTVER}"; \ + fi + @if [ -f $(REG_DLL_FILE8) ]; then \ + echo Installing $(REG_DLL_FILE8); \ + $(COPY) $(REG_DLL_FILE8) "$(LIB_INSTALL_DIR)/registry${REGDOTVER}"; \ + fi + @if [ -f $(REG_LIB_FILE) ]; then \ + echo Installing $(REG_LIB_FILE); \ + $(COPY) $(REG_LIB_FILE) "$(LIB_INSTALL_DIR)/registry${REGDOTVER}"; \ + fi + @echo "Installing pkg-config file to $(LIB_INSTALL_DIR)/pkgconfig/" + @$(INSTALL_DATA_DIR) "$(LIB_INSTALL_DIR)/pkgconfig" + @$(INSTALL_DATA) tcl.pc "$(LIB_INSTALL_DIR)/pkgconfig/tcl.pc" + +install-libraries: libraries install-tzdata install-msgs + @for i in "$(prefix)/lib" "$(INCLUDE_INSTALL_DIR)" \ + "$(SCRIPT_INSTALL_DIR)" "$(MODULE_INSTALL_DIR)"; \ + do \ + if [ ! -d "$$i" ] ; then \ + echo "Making directory $$i"; \ + $(MKDIR) "$$i"; \ + else true; \ + fi; \ + done; + @for i in opt0.4 cookiejar0.2 encoding; \ + do \ + if [ ! -d "$(SCRIPT_INSTALL_DIR)/$$i" ] ; then \ + echo "Making directory $(SCRIPT_INSTALL_DIR)/$$i"; \ + $(MKDIR) "$(SCRIPT_INSTALL_DIR)/$$i"; \ + else true; \ + fi; \ + done; + @for i in 9.0 9.0/platform; \ + do \ + if [ ! -d "$(MODULE_INSTALL_DIR)/$$i" ] ; then \ + echo "Making directory $(MODULE_INSTALL_DIR)/$$i"; \ + $(MKDIR) "$(MODULE_INSTALL_DIR)/$$i"; \ + else true; \ + fi; \ + done; + @echo "Installing library files to $(SCRIPT_INSTALL_DIR)"; + @for i in $(ROOT_DIR)/library/*.tcl $(ROOT_DIR)/library/tclIndex; do \ + $(COPY) "$$i" "$(SCRIPT_INSTALL_DIR)"; \ + done; + @echo "Installing package cookiejar 0.2" + @for j in $(ROOT_DIR)/library/cookiejar/*.tcl \ + $(ROOT_DIR)/library/cookiejar/*.gz; do \ + $(COPY) "$$j" "$(SCRIPT_INSTALL_DIR)/cookiejar0.2"; \ + done; + @echo "Installing package http 2.10.2 as a Tcl Module"; + @$(COPY) $(ROOT_DIR)/library/http/http.tcl "$(MODULE_INSTALL_DIR)/9.0/http-2.10.2.tm"; + @echo "Installing package opt 0.4.10"; + @for j in $(ROOT_DIR)/library/opt/*.tcl; do \ + $(COPY) "$$j" "$(SCRIPT_INSTALL_DIR)/opt0.4"; \ + done; + @echo "Installing package msgcat 1.7.1 as a Tcl Module"; + @$(COPY) $(ROOT_DIR)/library/msgcat/msgcat.tcl "$(MODULE_INSTALL_DIR)/9.0/msgcat-1.7.1.tm"; + @echo "Installing package tcltest 2.5.11 as a Tcl Module"; + @$(COPY) $(ROOT_DIR)/library/tcltest/tcltest.tcl "$(MODULE_INSTALL_DIR)/9.0/tcltest-2.5.11.tm"; + @echo "Installing package platform 1.1.0 as a Tcl Module"; + @$(COPY) $(ROOT_DIR)/library/platform/platform.tcl "$(MODULE_INSTALL_DIR)/9.0/platform-1.1.0.tm"; + @echo "Installing package platform::shell 1.1.4 as a Tcl Module"; + @$(COPY) $(ROOT_DIR)/library/platform/shell.tcl "$(MODULE_INSTALL_DIR)/9.0/platform/shell-1.1.4.tm"; + @echo "Installing encodings"; + @for i in $(ROOT_DIR)/library/encoding/*.enc ; do \ + $(COPY) "$$i" "$(SCRIPT_INSTALL_DIR)/encoding"; \ + done; + +install-tzdata: + @echo "Installing time zone data" + @$(TCL_EXE) "$(ROOT_DIR)/tools/installData.tcl" \ + "$(ROOT_DIR)/library/tzdata" "$(SCRIPT_INSTALL_DIR_NATIVE)/tzdata" + +install-msgs: + @echo "Installing message catalogs" + $(TCL_EXE) "$(ROOT_DIR)/tools/installData.tcl" \ + "$(ROOT_DIR)/library/msgs" "$(SCRIPT_INSTALL_DIR_NATIVE)/msgs" + +install-doc: doc + @for i in "$(MAN_INSTALL_DIR)" "$(MAN1_INSTALL_DIR)" "$(MAN3_INSTALL_DIR)" "$(MANN_INSTALL_DIR)" ; \ + do \ + if [ ! -d "$$i" ] ; then \ + echo "Making directory $$i"; \ + $(INSTALL_DATA_DIR) "$$i"; \ + else true; \ + fi; \ + done; + @echo "Installing and cross-linking top-level (.1) docs to $(MAN1_INSTALL_DIR)/"; + @for i in $(TOP_DIR)/doc/*.1; do \ + $(SHELL) $(UNIX_DIR)/installManPage $(MAN_FLAGS) $$i "$(MAN1_INSTALL_DIR)"; \ + done + @echo "Installing and cross-linking C API (.3) docs to $(MAN3_INSTALL_DIR)/"; + @for i in $(TOP_DIR)/doc/*.3; do \ + $(SHELL) $(UNIX_DIR)/installManPage $(MAN_FLAGS) $$i "$(MAN3_INSTALL_DIR)"; \ + done + @echo "Installing and cross-linking command (.n) docs to $(MANN_INSTALL_DIR)/"; + @for i in $(TOP_DIR)/doc/*.n; do \ + $(SHELL) $(UNIX_DIR)/installManPage $(MAN_FLAGS) $$i "$(MANN_INSTALL_DIR)"; \ + done + +install-headers: + @for i in "$(INCLUDE_INSTALL_DIR)"; \ + do \ + if [ ! -d "$$i" ] ; then \ + echo "Making directory $$i"; \ + $(MKDIR) "$$i"; \ + chmod 755 "$$i"; \ + else true; \ + fi; \ + done; + @echo "Installing header files to $(INCLUDE_INSTALL_DIR)/"; + @for i in "$(GENERIC_DIR)/tcl.h" "$(GENERIC_DIR)/tclDecls.h" \ + "$(GENERIC_DIR)/tclOO.h" "$(GENERIC_DIR)/tclOODecls.h" \ + "$(GENERIC_DIR)/tclPlatDecls.h" \ + "$(GENERIC_DIR)/tclTomMath.h" \ + "$(GENERIC_DIR)/tclTomMathDecls.h"; \ + do \ + $(COPY) "$$i" "$(INCLUDE_INSTALL_DIR)"; \ + done; + +# Optional target to install private headers +install-private-headers: libraries + @for i in $(PRIVATE_INCLUDE_INSTALL_DIR); \ + do \ + if [ ! -d $$i ] ; then \ + echo "Making directory $$i"; \ + $(MKDIR) $$i; \ + else true; \ + fi; \ + done; + @echo "Installing private header files"; + @for i in "$(GENERIC_DIR)/tclInt.h" "$(GENERIC_DIR)/tclIntDecls.h" \ + "$(GENERIC_DIR)/tclIntPlatDecls.h" "$(GENERIC_DIR)/tclPort.h" \ + "$(GENERIC_DIR)/tclOOInt.h" "$(GENERIC_DIR)/tclOOIntDecls.h" \ + "$(WIN_DIR)/tclWinPort.h" ; \ + do \ + $(COPY) "$$i" "$(PRIVATE_INCLUDE_INSTALL_DIR)"; \ + done; + +# Specifying TESTFLAGS on the command line is the standard way to pass args to +# tcltest, i.e.: +# % make test TESTFLAGS="-verbose bps -file fileName.test" + +test: test-tcl test-packages + +test-tcl: tcltest + TCL_LIBRARY="$(LIBRARY_DIR)"; export TCL_LIBRARY; \ + $(WINE) ./$(TCLSH) "$(ROOT_DIR_NATIVE)/tests/all.tcl" $(TESTFLAGS) \ + -load "$(TEST_LOAD_FACILITIES)" + +# Useful target to launch a built tclsh with the proper path,... +runtest: tcltest + @TCL_LIBRARY="$(LIBRARY_DIR)"; export TCL_LIBRARY; \ + $(WINE) ./$(TCLSH) $(SCRIPT) $(TESTFLAGS) -load "$(TEST_LOAD_FACILITIES)" + +# This target can be used to run tclsh from the build directory via +# `make shell SCRIPT=foo.tcl` +shell: binaries + @TCL_LIBRARY="$(LIBRARY_DIR)"; export TCL_LIBRARY; \ + $(WINE) ./$(TCLSH) $(SCRIPT) + +# This target can be used to run tclsh inside either gdb or insight +gdb: binaries + @echo "set env TCL_LIBRARY=$(LIBRARY_DIR)" > gdb.run + $(GDB) ./$(TCLSH) --command=gdb.run + rm gdb.run + +shquotequote = $(subst ',\",$(subst ",\",$(1))) +gdb-test: tcltest + @printf '%s ' 'set env TCL_LIBRARY=$(LIBRARY_DIR)' > gdb.run + @printf '\n' >>gdb.run + @printf '%s ' set args $(ROOT_DIR_NATIVE)/tests/all.tcl \ + $(call shquotequote,$(TESTFLAGS)) -singleproc 1 >> gdb.run + $(GDB) ${TEST_EXE_FILE} --command=gdb.run + rm gdb.run + +depend: + +Makefile: $(SRC_DIR)/Makefile.in + ./config.status + +cleanhelp: + $(RM) *.hlp *.cnt *.GID + +clean: cleanhelp clean-packages + $(RM) *.lib *.a *.exp *.dll *.$(RES) *.${OBJEXT} *~ \#* TAGS a.out + $(RM) $(TCLSH) $(CAT32) $(TEST_EXE_FILE) $(TEST_DLL_FILE) tcltest.cmd tcltest.sh + # remaining binaries (inclusive manifests) by version/branch switch, but retain tclsh.exe.manifest (that created on configure phase) + find . -maxdepth 1 -type f -regex '\./tcl\(sh[^.]+\|test\|[^.]+.dll\)\.[^.]*\(\.manifest\)?' \ + -a -not -name 'tclsh.exe.manifest' -exec rm {} \; + $(RM) *.pch *.ilk *.pdb *.zip + $(RM) minizip${HOST_EXEEXT} *.${HOST_OBJEXT} + $(RMDIR) *.vfs + +distclean: distclean-packages clean + $(RM) Makefile config.status config.cache config.log tclConfig.sh \ + config.status.lineno tclsh.exe.manifest tclUuid.h + +# +# Bundled package targets +# + +PKG_DIR = ./pkgs + +packages: + @builddir="`$(CYGPATH) "$$(pwd -P)"`"; \ + for i in '$(PKGS_DIR)'/*/; do \ + if [ -x $$i/configure ] ; then \ + pkg=`basename $$i`; \ + mkdir -p '$(PKG_DIR)'/$$pkg; \ + if [ ! -f '$(PKG_DIR)'/$$pkg/Makefile ]; then \ + ( cd '$(PKG_DIR)'/$$pkg; \ + echo "Configuring package '$$i' wd = `$(CYGPATH) $$(pwd -P)`"; \ + $$i/configure --with-tcl=$$builddir --with-tclinclude=$(GENERIC_DIR_NATIVE) $(PKG_CFG_ARGS) --enable-shared; ) \ + fi ; \ + echo "Building package '$$pkg'"; \ + ( cd '$(PKG_DIR)'/$$pkg; $(MAKE); ) \ + fi; \ + done; \ + cd "$$builddir" + +install-packages: packages + @builddir=`pwd -P`; \ + for i in '$(PKGS_DIR)'/*; do \ + if [ -d $$i ]; then \ + pkg=`basename $$i`; \ + if [ -f '$(PKG_DIR)'/$$pkg/Makefile ]; then \ + echo "Installing package '$$pkg'"; \ + ( cd '$(PKG_DIR)'/$$pkg; $(MAKE) install "DESTDIR=$(INSTALL_ROOT)"; ) \ + fi; \ + fi; \ + done; \ + cd "$$builddir" + +test-packages: tcltest packages + @builddir=`pwd -P`; \ + for i in $(PKGS_DIR)/*; do \ + if [ -d $$i ]; then \ + pkg=`basename $$i`; \ + if [ -f '$(PKG_DIR)'/$$pkg/Makefile ]; then \ + echo "Testing package '$$pkg'"; \ + ( cd '$(PKG_DIR)'/$$pkg; $(MAKE) "LD_LIBRARY_PATH=$$builddir:${LD_LIBRARY_PATH}" "TCL_LIBRARY=${TCL_BUILDTIME_LIBRARY}" "TCLLIBPATH=$$builddir/pkgs" test "TCLSH_PROG=$$builddir/${TCLSH}"; ) \ + fi; \ + fi; \ + done; \ + cd "$$builddir" + +clean-packages: + @builddir=`pwd -P`; \ + for i in '$(PKGS_DIR)'/*; do \ + if [ -d $$i ]; then \ + pkg=`basename $$i`; \ + if [ -f '$(PKG_DIR)'/$$pkg/Makefile ]; then \ + ( cd '$(PKG_DIR)'/$$pkg; $(MAKE) clean; ) \ + fi; \ + fi; \ + done; \ + cd "$$builddir" + +distclean-packages: + @builddir=`pwd -P`; \ + for i in $(PKGS_DIR)/*; do \ + if [ -d $$i ]; then \ + pkg=`basename $$i`; \ + if [ -f '$(PKG_DIR)'/$$pkg/Makefile ]; then \ + ( cd '$(PKG_DIR)'/$$pkg; $(MAKE) distclean; ) \ + fi; \ + cd $$builddir; \ + rm -rf '$(PKG_DIR)'/$$pkg; \ + fi; \ + done; \ + rm -rf '$(PKG_DIR)' + +# +# Regenerate the stubs files. +# + +$(GENERIC_DIR)/tclStubInit.c: $(GENERIC_DIR)/tcl.decls \ + $(GENERIC_DIR)/tclInt.decls + @echo "Warning: tclStubInit.c may be out of date." + @echo "Developers may want to run \"make genstubs\" to regenerate." + @echo "This warning can be safely ignored, do not report as a bug!" + +genstubs: + $(TCL_EXE) "$(TOOL_DIR_NATIVE)/genStubs.tcl" \ + "$(GENERIC_DIR_NATIVE)" \ + "$(GENERIC_DIR_NATIVE)/tcl.decls" \ + "$(GENERIC_DIR_NATIVE)/tclInt.decls" \ + "$(GENERIC_DIR_NATIVE)/tclTomMath.decls" + $(TCL_EXE) "$(TOOL_DIR_NATIVE)/genStubs.tcl" \ + "$(GENERIC_DIR_NATIVE)" \ + "$(GENERIC_DIR_NATIVE)/tclOO.decls" + +# +# This target creates the HTML folder for Tcl & Tk and places it in +# DISTDIR/html. It uses the tcltk-man2html.tcl tool from the Tcl group's tool +# workspace. It depends on the Tcl & Tk being in directories called tcl9.* & +# tk8.* up two directories from the TOOL_DIR. +# + +TOOL_DIR=$(ROOT_DIR)/tools +HTML_INSTALL_DIR=$(ROOT_DIR)/html +html: + $(MAKE) shell SCRIPT="$(TOOL_DIR)/tcltk-man2html.tcl --htmldir=$(HTML_INSTALL_DIR) --srcdir=$(ROOT_DIR)/.. $(BUILD_HTML_FLAGS)" + +html-tcl: $(TCLSH) + $(MAKE) shell SCRIPT="$(TOOL_DIR)/tcltk-man2html.tcl --htmldir=$(HTML_INSTALL_DIR) --srcdir=$(ROOT_DIR)/.. $(BUILD_HTML_FLAGS) --tcl" + +html-tk: $(TCLSH) + $(MAKE) shell SCRIPT="$(TOOL_DIR)/tcltk-man2html.tcl --htmldir=$(HTML_INSTALL_DIR) --srcdir=$(ROOT_DIR)/.. $(BUILD_HTML_FLAGS) --tk" + +# +# The list of all the targets that do not correspond to real files. This stops +# 'make' from getting confused when someone makes an error in a rule. +# + +.PHONY: all tcltest binaries libraries doc gendate gentommath_h install +.PHONY: install-binaries install-libraries install-tzdata install-msgs +.PHONY: install-doc install-private-headers test test-tcl runtest shell +.PHONY: gdb depend cleanhelp clean distclean packages install-packages +.PHONY: test-packages clean-packages distclean-packages genstubs html +.PHONY: html-tcl html-tk genscript +.PHONY: tclzipfile + +# DO NOT DELETE THIS LINE -- make depend depends on it. diff --git a/vendor/tcl/win/README b/vendor/tcl/win/README new file mode 100644 index 00000000..9b001ba0 --- /dev/null +++ b/vendor/tcl/win/README @@ -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. diff --git a/vendor/tcl/win/aclocal.m4 b/vendor/tcl/win/aclocal.m4 new file mode 100644 index 00000000..bc7540da --- /dev/null +++ b/vendor/tcl/win/aclocal.m4 @@ -0,0 +1 @@ +builtin(include,tcl.m4) diff --git a/vendor/tcl/win/buildall.vc.bat b/vendor/tcl/win/buildall.vc.bat new file mode 100755 index 00000000..53010654 --- /dev/null +++ b/vendor/tcl/win/buildall.vc.bat @@ -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 diff --git a/vendor/tcl/win/cat.c b/vendor/tcl/win/cat.c new file mode 100644 index 00000000..f8a6487e --- /dev/null +++ b/vendor/tcl/win/cat.c @@ -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 +#include +#include +#include + +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; +} diff --git a/vendor/tcl/win/configure b/vendor/tcl/win/configure new file mode 100755 index 00000000..5023fb70 --- /dev/null +++ b/vendor/tcl/win/configure @@ -0,0 +1,7227 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.73 for tcl 9.0. +# +# +# Copyright (C) 1992-1996, 1998-2017, 2020-2026 Free Software Foundation, +# Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # contradicts POSIX and common usage. Disable this. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else case e in #( + e) case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac ;; +esac +fi + + + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. +as_nl=' +' +export as_nl +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi + +# The user is always right. +if ${PATH_SEPARATOR+false} :; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as 'sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +case $# in # (( + 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; + *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; +esac +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed 'exec'. +printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # contradicts POSIX and common usage. Disable this. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else case e in #( + e) case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ) +then : + +else case e in #( + e) exitcode=1; echo positional parameters were not saved. ;; +esac +fi +test x\$exitcode = x0 || exit 1 +blah=\$(echo \$(echo blah)) +test x\"\$blah\" = xblah || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null +then : + as_have_required=yes +else case e in #( + e) as_have_required=no ;; +esac +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null +then : + +else case e in #( + e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : + CONFIG_SHELL=$as_shell as_have_required=yes + if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null +then : + break 2 +fi +fi + done;; + esac + as_found=false +done +IFS=$as_save_IFS +if $as_found +then : + +else case e in #( + e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi ;; +esac +fi + + + if test "x$CONFIG_SHELL" != x +then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +case $# in # (( + 0) exec $CONFIG_SHELL $as_opts "$as_myself" ;; + *) exec $CONFIG_SHELL $as_opts "$as_myself" "$@" ;; +esac +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed 'exec'. +printf '%s\n' "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno +then : + printf '%s\n' "$0: This script requires a shell more modern than all" + printf '%s\n' "$0: the shells that I found on your system." + if test ${ZSH_VERSION+y} ; then + printf '%s\n' "$0: In particular, zsh $ZSH_VERSION has bugs and should" + printf '%s\n' "$0: be upgraded to zsh 4.3.4 or later." + else + printf '%s\n' "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi ;; +esac +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else case e in #( + e) as_fn_append () + { + eval $1=\$$1\$2 + } ;; +esac +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else case e in #( + e) as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } ;; +esac +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + printf '%s\n' "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + t clear + :clear + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { printf '%s\n' "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated + +# Sed expression to map a string onto a valid variable name. +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_CONFIG_STATUS= +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='tcl' +PACKAGE_TARNAME='tcl' +PACKAGE_VERSION='9.0' +PACKAGE_STRING='tcl 9.0' +PACKAGE_BUGREPORT='' +PACKAGE_URL='' + +ac_unique_file="../generic/tcl.h" +ac_subst_vars='LTLIBOBJS +LIBOBJS +RES +RC_DEFINES +RC_DEFINE +RC_INCLUDE +RC_TYPE +RC_OUT +TCL_REG_MINOR_VERSION +TCL_REG_MAJOR_VERSION +TCL_REG_VERSION +TCL_DDE_MINOR_VERSION +TCL_DDE_MAJOR_VERSION +TCL_DDE_VERSION +TCL_PACKAGE_PATH +TCL_BUILD_LIB_SPEC +MAKE_EXE +MAKE_DLL +POST_MAKE_LIB +MAKE_STUB_LIB +MAKE_LIB +LIBRARIES +EXESUFFIX +LIBSUFFIX +LIBPREFIX +DLLSUFFIX +LIBS_GUI +TCL_SHARED_BUILD +SHLIB_SUFFIX +SHLIB_CFLAGS +SHLIB_LD_LIBS +SHLIB_LD +STLIB_LD +LDFLAGS_WINDOW +LDFLAGS_CONSOLE +LDFLAGS_OPTIMIZE +LDFLAGS_DEBUG +CC_EXENAME +CC_OBJNAME +DEPARG +EXTRA_CFLAGS +CFG_TCL_UNSHARED_LIB_SUFFIX +CFG_TCL_SHARED_LIB_SUFFIX +TCL_BIN_DIR +TCL_SRC_DIR +TCL_DLL_FILE +TCL_BUILD_STUB_LIB_PATH +TCL_BUILD_STUB_LIB_SPEC +TCL_INCLUDE_SPEC +TCL_STUB_LIB_PATH +TCL_STUB_LIB_SPEC +TCL_STUB_LIB_FLAG +TCL_STUB_LIB_FILE +TCL_LIB_SPEC +TCL_LIBS +TCL_IMPORT_LIB_FLAG +TCL_IMPORT_LIB_FILE +TCL_STATIC_LIB_FLAG +TCL_STATIC_LIB_FILE +TCL_LIB_FLAG +TCL_LIB_FILE +TCL_EXE +PKG_CFG_ARGS +TCL_PATCH_LEVEL +TCL_MINOR_VERSION +TCL_MAJOR_VERSION +TCL_VERSION +MACHINE +TCL_WIN_VERSION +VC_MANIFEST_EMBED_EXE +VC_MANIFEST_EMBED_DLL +CPP +LDFLAGS_DEFAULT +CFLAGS_DEFAULT +INSTALL_MSGS +INSTALL_LIBRARIES +TCL_ZIP_FILE +ZIPFS_BUILD +ZIP_INSTALL_OBJS +ZIP_PROG_VFSSEARCH +ZIP_PROG_OPTIONS +ZIP_PROG +TCLSH_PROG +EXEEXT_FOR_BUILD +CC_FOR_BUILD +TCL_TOMMATH_LIB_NAME +TCL_ZLIB_LIB_NAME +TOMMATH_OBJS +ZLIB_OBJS +TOMMATH_LIBS +ZLIB_LIBS +TOMMATH_DLL_FILE +ZLIB_DLL_FILE +CFLAGS_NOLTO +CFLAGS_WARNING +CFLAGS_OPTIMIZE +CFLAGS_DEBUG +DL_LIBS +WINE +CYGPATH +SHARED_BUILD +SET_MAKE +RC +RANLIB +AR +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +ECHO_T +ECHO_N +ECHO_C +target_alias +host_alias +build_alias +LIBS +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +runstatedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL +OBJEXT_FOR_BUILD' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +with_encoding +enable_shared +enable_64bit +enable_zipfs +enable_symbols +enable_embedded_manifest +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +CPP' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf '%s\n' "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: '$ac_option' +Try '$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: '$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + printf '%s\n' "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + printf '%s\n' "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`printf '%s\n' $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir runstatedir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: '$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +'configure' configures tcl 9.0 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print 'checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for '--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or '..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, 'make install' will install all the files in +'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify +an installation prefix other than '$ac_default_prefix' using '--prefix', +for instance '--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/tcl] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of tcl 9.0:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-shared build and link with shared libraries (default: on) + --enable-64bit enable 64bit support (where applicable) + --enable-zipfs build with Zipfs support (default: on) + --enable-symbols build with debugging symbols (default: off) + --enable-embedded-manifest + embed manifest if possible (default: yes) + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-encoding encoding for configuration values + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + CPP C preprocessor + +Use these variables to override the choices made by 'configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for configure.gnu first; this name is used for a wrapper for + # Metaconfig's "Configure" on case-insensitive file systems. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + printf '%s\n' "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +tcl configure 9.0 +generated by GNU Autoconf 2.73 + +Copyright (C) 2026 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest.beam + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext +then : + ac_retval=0 +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 ;; +esac +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + } +then : + ac_retval=0 +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 ;; +esac +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_type LINENO TYPE VAR INCLUDES +# ------------------------------------------- +# Tests whether TYPE exists after having included INCLUDES, setting cache +# variable VAR accordingly. +ac_fn_c_check_type () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +printf %s "checking for $2... " >&6; } +if eval test \${$3+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) eval "$3=no" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main (void) +{ +if (sizeof ($2)) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main (void) +{ +if (sizeof (($2))) + return 0; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + +else case e in #( + e) eval "$3=yes" ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +eval ac_res=\$$3 + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf '%s\n' "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_type + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + } +then : + ac_retval=0 +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 ;; +esac +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp +ac_configure_args_raw= +for ac_arg +do + case $ac_arg in + *\'*) + ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append ac_configure_args_raw " '$ac_arg'" +done + +case $ac_configure_args_raw in + *$as_nl*) + ac_safe_unquote= ;; + *) + ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. + ac_unsafe_a="$ac_unsafe_z#~" + ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" + ac_configure_args_raw=` printf '%s\n' "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; +esac + +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by tcl $as_me 9.0, which was +generated by GNU Autoconf 2.73. Invocation command line was + + $ $0$ac_configure_args_raw + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + printf '%s\n' "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`printf '%s\n' "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# Dump the cache to stdout. It can be in a pipe (this is a requirement). +ac_cache_dump () +{ + # The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +printf '%s\n' "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # 'set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # 'set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) +} + +# Print debugging info to stdout. +ac_dump_debugging_info () +{ + echo + + printf '%s\n' "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + ac_cache_dump + echo + + printf '%s\n' "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; + esac + printf '%s\n' "$ac_var='$ac_val'" + done | sort + echo + + if test -n "$ac_subst_files"; then + printf '%s\n' "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'*) ac_val=`printf '%s\n' "$ac_val" | sed "s/'/'\\\\\\\\''/g"`;; + esac + printf '%s\n' "$ac_var='$ac_val'" + done | sort + echo + fi + + if test -s confdefs.h; then + printf '%s\n' "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + printf '%s\n' "$as_me: caught signal $ac_signal" + printf '%s\n' "$as_me: exit $exit_status" +} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. +ac_exit_trap () +{ + exit_status= + # Sanitize IFS. + IFS=" "" $as_nl" + # Save into config.log some information that might help in debugging. + ac_dump_debugging_info >&5 + eval "rm -f $ac_clean_CONFIG_STATUS core *.core core.conftest.*" && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +} + +trap 'ac_exit_trap $?' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +printf '%s\n' "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +printf '%s\n' "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h + +printf '%s\n' "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +if test -n "$CONFIG_SITE"; then + ac_site_files="$CONFIG_SITE" +elif test "x$prefix" != xNONE; then + ac_site_files="$prefix/share/config.site $prefix/etc/config.site" +else + ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" +fi + +for ac_site_file in $ac_site_files +do + case $ac_site_file in #( + */*) : + ;; #( + *) : + ac_site_file=./$ac_site_file ;; +esac + if test -f "$ac_site_file" && test -r "$ac_site_file"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +printf '%s\n' "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See 'config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +printf '%s\n' "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +printf '%s\n' "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Test code for whether the C compiler supports C23 (global declarations) +ac_c_conftest_c23_globals=' +/* Does the compiler advertise conformance to C17 or earlier? + Although GCC 14 does not do that, even with -std=gnu23, + it is close enough, and defines __STDC_VERSION == 202000L. */ +#if !defined __STDC_VERSION__ || __STDC_VERSION__ <= 201710L +# error "Compiler advertises conformance to C17 or earlier" +#endif + +// Check alignas. +char alignas (double) c23_aligned_as_double; +char alignas (0) c23_no_special_alignment; +extern char c23_aligned_as_int; +char alignas (0) alignas (int) c23_aligned_as_int; + +// Check alignof. +enum +{ + c23_int_alignment = alignof (int), + c23_int_array_alignment = alignof (int[100]), + c23_char_alignment = alignof (char) +}; +static_assert (0 < -alignof (int), "alignof is signed"); + +int function_with_unnamed_parameter (int) { return 0; } + +void c23_noreturn (); + +/* Test parsing of string and char UTF-8 literals (including hex escapes). + The parens pacify GCC 15. */ +bool use_u8 = (!sizeof u8"\xFF") == (!u8'\''x'\''); + +bool check_that_bool_works = true | false | !nullptr; +#if !true +# error "true does not work in #if" +#endif +#if false +#elifdef __STDC_VERSION__ +#else +# error "#elifdef does not work" +#endif + +#ifndef __has_c_attribute +# error "__has_c_attribute not defined" +#endif + +#ifndef __has_include +# error "__has_include not defined" +#endif + +#define LPAREN() ( +#define FORTY_TWO(x) 42 +#define VA_OPT_TEST(r, x, ...) __VA_OPT__ (FORTY_TWO r x)) +static_assert (VA_OPT_TEST (LPAREN (), 0, <:-) == 42); + +static_assert (0b101010 == 42); +static_assert (0B101010 == 42); +static_assert (0xDEAD'\''BEEF == 3'\''735'\''928'\''559); +static_assert (0.500'\''000'\''000 == 0.5); + +enum unsignedish : unsigned int { uione = 1 }; +static_assert (0 < -uione); + +#include +constexpr nullptr_t null_pointer = nullptr; + +static typeof (1 + 1L) two () { return 2; } +static long int three () { return 3; } +' + +# Test code for whether the C compiler supports C23 (body of main). +ac_c_conftest_c23_main=' + { + label_before_declaration: + int arr[10] = {}; + if (arr[0]) + goto label_before_declaration; + if (!arr[0]) + goto label_at_end_of_block; + label_at_end_of_block: + } + ok |= !null_pointer; + ok |= two != three; +' + +# Test code for whether the C compiler supports C23 (complete). +ac_c_conftest_c23_program="${ac_c_conftest_c23_globals} + +int +main (int, char **) +{ + int ok = 0; + ${ac_c_conftest_c23_main} + return ok; +} +" + +# Test code for whether the C compiler supports C89 (global declarations) +ac_c_conftest_c89_globals=' +/* Do not test the value of __STDC__, because some compilers define it to 0 + or do not define it, while otherwise adequately conforming. */ + +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ +struct buf { int x; }; +struct buf * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (char **p, int i) +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* C89 style stringification. */ +#define noexpand_stringify(a) #a +const char *stringified = noexpand_stringify(arbitrary+token=sequence); + +/* C89 style token pasting. Exercises some of the corner cases that + e.g. old MSVC gets wrong, but not very hard. */ +#define noexpand_concat(a,b) a##b +#define expand_concat(a,b) noexpand_concat(a,b) +extern int vA; +extern int vbee; +#define aye A +#define bee B +int *pvA = &expand_concat(v,aye); +int *pvbee = &noexpand_concat(v,bee); + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not \xHH hex character constants. + These do not provoke an error unfortunately, instead are silently treated + as an "x". The following induces an error, until -std is added to get + proper ANSI mode. Curiously \x00 != x always comes out true, for an + array size at least. It is necessary to write \x00 == 0 to get something + that is true only with -std. */ +int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) '\''x'\'' +int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), + int, int);' + +# Test code for whether the C compiler supports C89 (body of main). +ac_c_conftest_c89_main=' +ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); +' + +# Test code for whether the C compiler supports C99 (global declarations) +ac_c_conftest_c99_globals=' +/* Does the compiler advertise C99 conformance? */ +#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L +# error "Compiler does not advertise C99 conformance" +#endif + +// See if C++-style comments work. + +#include +extern int puts (const char *); +extern int printf (const char *, ...); +extern int dprintf (int, const char *, ...); +extern void *malloc (size_t); +extern void free (void *); + +// Check varargs macros. These examples are taken from C99 6.10.3.5. +// dprintf is used instead of fprintf to avoid needing to declare +// FILE and stderr, and "aND" is used instead of "and" to work around +// GCC bug 40564 which is irrelevant here. +#define debug(...) dprintf (2, __VA_ARGS__) +#define showlist(...) puts (#__VA_ARGS__) +#define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) +static void +test_varargs_macros (void) +{ + int x = 1234; + int y = 5678; + debug ("Flag"); + debug ("X = %d\n", x); + showlist (The first, second, aND third items.); + report (x>y, "x is %d but y is %d", x, y); +} + +// Check long long types. +#define BIG64 18446744073709551615ull +#define BIG32 4294967295ul +#define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) +#if !BIG_OK + #error "your preprocessor is broken" +#endif +#if BIG_OK +#else + #error "your preprocessor is broken" +#endif +static long long int bignum = -9223372036854775807LL; +static unsigned long long int ubignum = BIG64; + +struct incomplete_array +{ + int datasize; + double data[]; +}; + +struct named_init { + int number; + const wchar_t *name; + double average; +}; + +typedef const char *ccp; + +static inline int +test_restrict (ccp restrict text) +{ + // Iterate through items via the restricted pointer. + // Also check for declarations in for loops. + for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) + continue; + return 0; +} + +// Check varargs and va_copy. +static bool +test_varargs (const char *format, ...) +{ + va_list args; + va_start (args, format); + va_list args_copy; + va_copy (args_copy, args); + + const char *str = ""; + int number = 0; + float fnumber = 0; + + while (*format) + { + switch (*format++) + { + case '\''s'\'': // string + str = va_arg (args_copy, const char *); + break; + case '\''d'\'': // int + number = va_arg (args_copy, int); + break; + case '\''f'\'': // float + fnumber = va_arg (args_copy, double); + break; + default: + break; + } + } + va_end (args_copy); + va_end (args); + + return *str && number && fnumber; +} +' + +# Test code for whether the C compiler supports C99 (body of main). +ac_c_conftest_c99_main=' + // Check bool. + _Bool success = false; + success |= (argc != 0); + + // Check restrict. + if (test_restrict ("String literal") == 0) + success = true; + const char *restrict newvar = "Another string"; + + // Check varargs. + success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); + test_varargs_macros (); + + // Check flexible array members. + static struct incomplete_array *volatile incomplete_array_pointer; + struct incomplete_array *ia = incomplete_array_pointer; + ia->datasize = 10; + for (int i = 0; i < ia->datasize; ++i) + ia->data[i] = i * 1.234; + // Work around memory leak warnings. + free (ia); + + // Check named initializers. + struct named_init ni = { + .number = 34, + .name = L"Test wide string", + .average = 543.34343, + }; + + ni.number = 58; + + // Do not test for VLAs, as some otherwise-conforming compilers lack them. + // C code should instead use __STDC_NO_VLA__; see Autoconf manual. + + // work around unused variable warnings + ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' + || ni.number != 58); +' + +# Test code for whether the C compiler supports C11 (global declarations) +ac_c_conftest_c11_globals=' +/* Does the compiler advertise C11 conformance? */ +#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L +# error "Compiler does not advertise C11 conformance" +#endif + +// Check _Alignas. +char _Alignas (double) aligned_as_double; +char _Alignas (0) no_special_alignment; +extern char aligned_as_int; +char _Alignas (0) _Alignas (int) aligned_as_int; + +// Check _Alignof. +enum +{ + int_alignment = _Alignof (int), + int_array_alignment = _Alignof (int[100]), + char_alignment = _Alignof (char) +}; +_Static_assert (0 < -_Alignof (int), "_Alignof is signed"); + +// Check _Noreturn. +int _Noreturn does_not_return (void) { for (;;) continue; } + +// Check _Static_assert. +struct test_static_assert +{ + int x; + _Static_assert (sizeof (int) <= sizeof (long int), + "_Static_assert does not work in struct"); + long int y; +}; + +// Check UTF-8 literals. +#define u8 syntax error! +char const utf8_literal[] = u8"happens to be ASCII" "another string"; + +// Check duplicate typedefs. +typedef long *long_ptr; +typedef long int *long_ptr; +typedef long_ptr long_ptr; + +// Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. +struct anonymous +{ + union { + struct { int i; int j; }; + struct { int k; long int l; } w; + }; + int m; +} v1; +' + +# Test code for whether the C compiler supports C11 (body of main). +ac_c_conftest_c11_main=' + _Static_assert ((offsetof (struct anonymous, i) + == offsetof (struct anonymous, w.k)), + "Anonymous union alignment botch"); + v1.i = 2; + v1.w.k = 5; + ok |= v1.i != 5; +' + +# Test code for whether the C compiler supports C11 (complete). +ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} +${ac_c_conftest_c99_globals} +${ac_c_conftest_c11_globals} + +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_c_conftest_c89_main} + ${ac_c_conftest_c99_main} + ${ac_c_conftest_c11_main} + return ok; +} +" + +# Test code for whether the C compiler supports C99 (complete). +ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} +${ac_c_conftest_c99_globals} + +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_c_conftest_c89_main} + ${ac_c_conftest_c99_main} + return ok; +} +" + +# Test code for whether the C compiler supports C89 (complete). +ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} + +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_c_conftest_c89_main} + return ok; +} +" + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 +printf '%s\n' "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 +printf '%s\n' "$as_me: error: '$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w= + for ac_val in x $ac_old_val; do + ac_old_val_w="$ac_old_val_w $ac_val" + done + ac_new_val_w= + for ac_val in x $ac_new_val; do + ac_new_val_w="$ac_new_val_w $ac_val" + done + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 +printf '%s\n' "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 +printf '%s\n' "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 +printf '%s\n' "$as_me: former value: '$ac_old_val'" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 +printf '%s\n' "$as_me: current value: '$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`printf '%s\n' "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +printf '%s\n' "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' + and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + + +# Determine whether it's possible to make 'echo' print without a newline. +# These variables are no longer used directly by Autoconf, but are AC_SUBSTed +# for compatibility with existing Makefiles. +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + + +# 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_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +printf '%s\n' "$ac_ct_CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + if test "$as_dir$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir$ac_word${1+' '}$@" + fi +fi +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +printf '%s\n' "$ac_ct_CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}clang", so it can be a program name with args. +set dummy ${ac_tool_prefix}clang; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}clang" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +printf '%s\n' "$CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "clang", so it can be a program name with args. +set dummy clang; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_CC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="clang" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +printf '%s\n' "$ac_ct_CC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +fi + + +test -z "$CC" && { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See 'config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion -version; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +printf %s "checking whether the C compiler works... " >&6; } +ac_link_default=`printf '%s\n' "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. +# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an '-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else case e in #( + e) ac_file='' ;; +esac +fi +if test -z "$ac_file" +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See 'config.log' for more details" "$LINENO" 5; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +printf %s "checking for C compiler default output file name... " >&6; } +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +printf '%s\n' "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +printf %s "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) +# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will +# work properly (i.e., refer to 'conftest.exe'), while it won't with +# 'rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else case e in #( + e) { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See 'config.log' for more details" "$LINENO" 5; } ;; +esac +fi +rm -f conftest conftest$ac_cv_exeext +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +printf '%s\n' "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +FILE *f = fopen ("conftest.out", "w"); + if (!f) + return 1; + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +printf %s "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error 77 "cannot run C compiled programs. +If you meant to cross compile, use '--host'. +See 'config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +printf '%s\n' "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext \ + conftest.o conftest.obj conftest.out +ac_clean_files=$ac_clean_files_save +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +printf %s "checking for suffix of object files... " >&6; } +if test ${ac_cv_objext+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else case e in #( + e) printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See 'config.log' for more details" "$LINENO" 5; } ;; +esac +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +printf '%s\n' "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 +printf %s "checking whether the compiler supports GNU C... " >&6; } +if test ${ac_cv_c_compiler_gnu+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_compiler_gnu=yes +else case e in #( + e) ac_compiler_gnu=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +printf '%s\n' "$ac_cv_c_compiler_gnu" >&6; } +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+y} +ac_save_CFLAGS=$CFLAGS +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +printf %s "checking whether $CC accepts -g... " >&6; } +if test ${ac_cv_prog_cc_g+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_g=yes +else case e in #( + e) CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + +else case e in #( + e) ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +printf '%s\n' "$ac_cv_prog_cc_g" >&6; } +if test $ac_test_CFLAGS; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +ac_prog_cc_stdc=no +if test x$ac_prog_cc_stdc = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C23 features" >&5 +printf %s "checking for $CC option to enable C23 features... " >&6; } +if test ${ac_cv_prog_cc_c23+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cc_c23=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_c_conftest_c23_program +_ACEOF +for ac_arg in '' -std=gnu23 +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_c23=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cc_c23" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC ;; +esac +fi + +if test "x$ac_cv_prog_cc_c23" = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c23" = x +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c23" >&5 +printf '%s\n' "$ac_cv_prog_cc_c23" >&6; } + CC="$CC $ac_cv_prog_cc_c23" ;; +esac +fi + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c23 + ac_prog_cc_stdc=c23 ;; +esac +fi +fi +if test x$ac_prog_cc_stdc = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 +printf %s "checking for $CC option to enable C11 features... " >&6; } +if test ${ac_cv_prog_cc_c11+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cc_c11=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_c_conftest_c11_program +_ACEOF +for ac_arg in '' -std=gnu11 -std:c11 +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_c11=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cc_c11" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC ;; +esac +fi + +if test "x$ac_cv_prog_cc_c11" = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c11" = x +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 +printf '%s\n' "$ac_cv_prog_cc_c11" >&6; } + CC="$CC $ac_cv_prog_cc_c11" ;; +esac +fi + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 + ac_prog_cc_stdc=c11 ;; +esac +fi +fi +if test x$ac_prog_cc_stdc = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 +printf %s "checking for $CC option to enable C99 features... " >&6; } +if test ${ac_cv_prog_cc_c99+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cc_c99=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_c_conftest_c99_program +_ACEOF +for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_c99=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cc_c99" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC ;; +esac +fi + +if test "x$ac_cv_prog_cc_c99" = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c99" = x +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 +printf '%s\n' "$ac_cv_prog_cc_c99" >&6; } + CC="$CC $ac_cv_prog_cc_c99" ;; +esac +fi + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 + ac_prog_cc_stdc=c99 ;; +esac +fi +fi +if test x$ac_prog_cc_stdc = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 +printf %s "checking for $CC option to enable C89 features... " >&6; } +if test ${ac_cv_prog_cc_c89+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_c_conftest_c89_program +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC ;; +esac +fi + +if test "x$ac_cv_prog_cc_c89" = xno +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf '%s\n' "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cc_c89" = x +then : + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf '%s\n' "none needed" >&6; } +else case e in #( + e) { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +printf '%s\n' "$ac_cv_prog_cc_c89" >&6; } + CC="$CC $ac_cv_prog_cc_c89" ;; +esac +fi + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 + ac_prog_cc_stdc=c89 ;; +esac +fi +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for inline" >&5 +printf %s "checking for inline... " >&6; } +if test ${ac_cv_c_inline+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_c_inline=no +for ac_kw in inline __inline__ __inline; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifndef __cplusplus +typedef int foo_t; +static $ac_kw foo_t static_foo (void) {return 0; } +$ac_kw foo_t foo (void) {return 0; } +#endif + +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_c_inline=$ac_kw +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + test "$ac_cv_c_inline" != no && break +done + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_inline" >&5 +printf '%s\n' "$ac_cv_c_inline" >&6; } + +case $ac_cv_c_inline in + inline | yes) ;; + *) + case $ac_cv_c_inline in + no) ac_val=;; + *) ac_val=$ac_cv_c_inline;; + esac + cat >>confdefs.h <<_ACEOF +#ifndef __cplusplus +#define inline $ac_val +#endif +_ACEOF + ;; +esac + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ar", so it can be a program name with args. +set dummy ${ac_tool_prefix}ar; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_AR+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$AR"; then + ac_cv_prog_AR="$AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_AR="${ac_tool_prefix}ar" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +AR=$ac_cv_prog_AR +if test -n "$AR"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 +printf '%s\n' "$AR" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_AR"; then + ac_ct_AR=$AR + # Extract the first word of "ar", so it can be a program name with args. +set dummy ar; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_AR+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_AR"; then + ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_AR="ar" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_AR=$ac_cv_prog_ac_ct_AR +if test -n "$ac_ct_AR"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 +printf '%s\n' "$ac_ct_AR" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + if test "x$ac_ct_AR" = x; then + AR="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + AR=$ac_ct_AR + fi +else + AR="$ac_cv_prog_AR" +fi + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_RANLIB+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +printf '%s\n' "$RANLIB" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_RANLIB+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +printf '%s\n' "$ac_ct_RANLIB" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}windres", so it can be a program name with args. +set dummy ${ac_tool_prefix}windres; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_RC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$RC"; then + ac_cv_prog_RC="$RC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_RC="${ac_tool_prefix}windres" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +RC=$ac_cv_prog_RC +if test -n "$RC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $RC" >&5 +printf '%s\n' "$RC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RC"; then + ac_ct_RC=$RC + # Extract the first word of "windres", so it can be a program name with args. +set dummy windres; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_RC+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_RC"; then + ac_cv_prog_ac_ct_RC="$ac_ct_RC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RC="windres" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_RC=$ac_cv_prog_ac_ct_RC +if test -n "$ac_ct_RC"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RC" >&5 +printf '%s\n' "$ac_ct_RC" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + if test "x$ac_ct_RC" = x; then + RC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf '%s\n' "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RC=$ac_ct_RC + fi +else + RC="$ac_cv_prog_RC" +fi + + +#-------------------------------------------------------------------- +# Checks to see if the make program sets the $MAKE variable. +#-------------------------------------------------------------------- + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +printf %s "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`printf '%s\n' "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval test \${ac_cv_prog_make_${ac_make}_set+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @printf '%s\n' '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make ;; +esac +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } + SET_MAKE= +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + + +#-------------------------------------------------------------------- +# Determines the correct binary file extension (.o, .obj, .exe etc.) +#-------------------------------------------------------------------- + + + + +#------------------------------------------------------------------------ +# Embedded configuration information, encoding to use for the values, TIP #59 +#------------------------------------------------------------------------ + + + +# Check whether --with-encoding was given. +if test ${with_encoding+y} +then : + withval=$with_encoding; with_tcencoding=${withval} +fi + + + if test x"${with_tcencoding}" != x ; then + cat >>confdefs.h <<_ACEOF +#define TCL_CFGVAL_ENCODING "${with_tcencoding}" +_ACEOF + + else + printf '%s\n' "#define TCL_CFGVAL_ENCODING \"utf-8\"" >>confdefs.h + + fi + + +#-------------------------------------------------------------------- +# The statements below define a collection of symbols related to +# building libtcl as a shared library instead of a static library. +#-------------------------------------------------------------------- + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to build libraries" >&5 +printf %s "checking how to build libraries... " >&6; } + # Check whether --enable-shared was given. +if test ${enable_shared+y} +then : + enableval=$enable_shared; tcl_ok=$enableval +else case e in #( + e) tcl_ok=yes ;; +esac +fi + + if test "$tcl_ok" = "yes" ; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: shared" >&5 +printf '%s\n' "shared" >&6; } + SHARED_BUILD=1 + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: static" >&5 +printf '%s\n' "static" >&6; } + SHARED_BUILD=0 + +printf '%s\n' "#define STATIC_BUILD 1" >>confdefs.h + + fi + + + +#-------------------------------------------------------------------- +# 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. +#-------------------------------------------------------------------- + + + + # Step 0: Enable 64 bit support? + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if 64bit support is requested" >&5 +printf %s "checking if 64bit support is requested... " >&6; } + # Check whether --enable-64bit was given. +if test ${enable_64bit+y} +then : + enableval=$enable_64bit; do64bit=$enableval +else case e in #( + e) do64bit=no ;; +esac +fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $do64bit" >&5 +printf '%s\n' "$do64bit" >&6; } + + # Set some defaults (may get changed below) + EXTRA_CFLAGS="" + +printf '%s\n' "#define MODULE_SCOPE extern" >>confdefs.h + + + # Extract the first word of "cygpath", so it can be a program name with args. +set dummy cygpath; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CYGPATH+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CYGPATH"; then + ac_cv_prog_CYGPATH="$CYGPATH" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CYGPATH="cygpath -m" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_CYGPATH" && ac_cv_prog_CYGPATH="echo" +fi ;; +esac +fi +CYGPATH=$ac_cv_prog_CYGPATH +if test -n "$CYGPATH"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CYGPATH" >&5 +printf '%s\n' "$CYGPATH" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + + # Extract the first word of "wine", so it can be a program name with args. +set dummy wine; ac_word=$2 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_WINE+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$WINE"; then + ac_cv_prog_WINE="$WINE" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_WINE="wine" + printf '%s\n' "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +WINE=$ac_cv_prog_WINE +if test -n "$WINE"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $WINE" >&5 +printf '%s\n' "$WINE" >&6; } +else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +fi + + + + SHLIB_SUFFIX=".dll" + + # MACHINE is IX86 for LINK, but this is used by the manifest, + # which requires x86|amd64|arm64|ia64. + MACHINE="X86" + + if test "$GCC" = "yes"; then + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for cross-compile version of gcc" >&5 +printf %s "checking for cross-compile version of gcc... " >&6; } +if test ${ac_cv_cross+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #ifndef _WIN32 + #error cross-compiler + #endif + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_cross=no +else case e in #( + e) ac_cv_cross=yes ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cross" >&5 +printf '%s\n' "$ac_cv_cross" >&6; } + + if test "$ac_cv_cross" = "yes"; then + case "$do64bit" in + amd64|x64|yes) + CC="x86_64-w64-mingw32-${CC}" + LD="x86_64-w64-mingw32-ld" + AR="x86_64-w64-mingw32-ar" + RANLIB="x86_64-w64-mingw32-ranlib" + RC="x86_64-w64-mingw32-windres" + ;; + arm64|aarch64) + CC="aarch64-w64-mingw32-${CC}" + LD="aarch64-w64-mingw32-ld" + AR="aarch64-w64-mingw32-ar" + RANLIB="aarch64-w64-mingw32-ranlib" + RC="aarch64-w64-mingw32-windres" + ;; + *) + CC="i686-w64-mingw32-${CC}" + LD="i686-w64-mingw32-ld" + AR="i686-w64-mingw32-ar" + RANLIB="i686-w64-mingw32-ranlib" + RC="i686-w64-mingw32-windres" + ;; + esac + fi + fi + + # Check for a bug in gcc's windres that causes the + # compile to fail when a Windows native path is + # passed into windres. The mingw toolchain requires + # Windows native paths while Cygwin should work + # with both. Avoid the bug by passing a POSIX + # path when using the Cygwin toolchain. + + if test "$GCC" = "yes" && test "$CYGPATH" != "echo" ; then + conftest=/tmp/conftest.rc + echo "STRINGTABLE BEGIN" > $conftest + echo "101 \"name\"" >> $conftest + echo "END" >> $conftest + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for Windows native path bug in windres" >&5 +printf %s "checking for Windows native path bug in windres... " >&6; } + cyg_conftest=`$CYGPATH $conftest` + if { ac_try='$RC -o conftest.res.o $cyg_conftest' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5 + (eval $ac_try) 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; } ; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } + CYGPATH=echo + fi + conftest= + cyg_conftest= + fi + + if test "$CYGPATH" = "echo"; then + DEPARG='"$<"' + else + DEPARG='"$(shell $(CYGPATH) $<)"' + fi + + # set various compiler flags depending on whether we are using gcc or cl + + if test "${GCC}" = "yes" ; then + extra_cflags="-pipe" + extra_ldflags="-pipe -static-libgcc -municode" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for mingw32 version of gcc" >&5 +printf %s "checking for mingw32 version of gcc... " >&6; } +if test ${ac_cv_win32+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #ifdef _WIN32 + #error win32 + #endif + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_win32=no +else case e in #( + e) ac_cv_win32=yes ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_win32" >&5 +printf '%s\n' "$ac_cv_win32" >&6; } + if test "$ac_cv_win32" != "yes"; then + as_fn_error $? "${CC} cannot produce win32 executables." "$LINENO" 5 + fi + if test "$do64bit" != "arm64" -a "$do64bit" != "aarch64"; then + extra_cflags="$extra_cflags -DHAVE_CPUID=1" + fi + + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -fno-lto" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for working -fno-lto" >&5 +printf %s "checking for working -fno-lto... " >&6; } +if test ${ac_cv_nolto+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_nolto=yes +else case e in #( + e) ac_cv_nolto=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_nolto" >&5 +printf '%s\n' "$ac_cv_nolto" >&6; } + CFLAGS=$hold_cflags + if test "$ac_cv_nolto" = "yes" ; then + CFLAGS_NOLTO="-fno-lto" + else + CFLAGS_NOLTO="" + fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if the linker understands --disable-high-entropy-va" >&5 +printf %s "checking if the linker understands --disable-high-entropy-va... " >&6; } +if test ${tcl_cv_ld_high_entropy+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Wl,--disable-high-entropy-va" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_ld_high_entropy=yes +else case e in #( + e) tcl_cv_ld_high_entropy=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_ld_high_entropy" >&5 +printf '%s\n' "$tcl_cv_ld_high_entropy" >&6; } + if test $tcl_cv_ld_high_entropy = yes; then + extra_ldflags="$extra_ldflags -Wl,--disable-high-entropy-va" + fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking if the compiler understands -finput-charset" >&5 +printf %s "checking if the compiler understands -finput-charset... " >&6; } +if test ${tcl_cv_cc_input_charset+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -finput-charset=UTF-8" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_cc_input_charset=yes +else case e in #( + e) tcl_cv_cc_input_charset=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CFLAGS=$hold_cflags ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cc_input_charset" >&5 +printf '%s\n' "$tcl_cv_cc_input_charset" >&6; } + if test $tcl_cv_cc_input_charset = yes; then + extra_cflags="$extra_cflags -finput-charset=UTF-8" + fi + fi + + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Wl,--enable-auto-image-base" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for working --enable-auto-image-base" >&5 +printf %s "checking for working --enable-auto-image-base... " >&6; } +if test ${ac_cv_enable_auto_image_base+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_enable_auto_image_base=yes +else case e in #( + e) ac_cv_enable_auto_image_base=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_enable_auto_image_base" >&5 +printf '%s\n' "$ac_cv_enable_auto_image_base" >&6; } + CFLAGS=$hold_cflags + if test "$ac_cv_enable_auto_image_base" = "yes" ; then + extra_ldflags="$extra_ldflags -Wl,--enable-auto-image-base" + fi + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking compiler flags" >&5 +printf %s "checking compiler flags... " >&6; } + if test "${GCC}" = "yes" ; then + SHLIB_LD="" + SHLIB_LD_LIBS='${LIBS}' + LIBS="-lnetapi32 -lkernel32 -luser32 -ladvapi32 -luserenv -lws2_32" + # mingw needs to link ole32 and oleaut32 for [send], but MSVC doesn't + LIBS_GUI="-lgdi32 -lcomdlg32 -limm32 -lcomctl32 -lshell32 -luuid -lole32 -loleaut32 -lwinspool" + STLIB_LD='${AR} cr' + RC_OUT=-o + RC_TYPE= + RC_INCLUDE=--include + RC_DEFINE=--define + RES=res.o + MAKE_LIB="\${STLIB_LD} \$@" + MAKE_STUB_LIB="\${STLIB_LD} \$@" + POST_MAKE_LIB="\${RANLIB} \$@" + MAKE_EXE="\${CC} -o \$@" + LIBPREFIX="lib" + + if test "${SHARED_BUILD}" = "0" ; then + # static + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: using static flags" >&5 +printf '%s\n' "using static flags" >&6; } + runtime= + LIBRARIES="\${STATIC_LIBRARIES}" + EXESUFFIX="s.exe" + else + # dynamic + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: using shared flags" >&5 +printf '%s\n' "using shared flags" >&6; } + + # ad-hoc check to see if CC supports -shared. + if "${CC}" -shared 2>&1 | egrep ': -shared not supported' >/dev/null; then + as_fn_error $? "${CC} does not support the -shared option. + You will need to upgrade to a newer version of the toolchain." "$LINENO" 5 + fi + + runtime= + # Add SHLIB_LD_LIBS to the Make rule, not here. + + EXESUFFIX=".exe" + LIBRARIES="\${SHARED_LIBRARIES}" + fi + # Link with gcc since ld does not link to default libs like + # -luser32 and -lmsvcrt by default. + SHLIB_LD='${CC} -shared' + SHLIB_LD_LIBS='${LIBS}' + MAKE_DLL="\${SHLIB_LD} \$(LDFLAGS) -o \$@ ${extra_ldflags} \ + -Wl,--out-implib,\$(patsubst %.dll,lib%.dll.a,\$@)" + # DLLSUFFIX is separate because it is the building block for + # users of tclConfig.sh that may build shared or static. + DLLSUFFIX=".dll" + LIBSUFFIX=".a" + LIBFLAGSUFFIX="" + SHLIB_SUFFIX=.dll + + EXTRA_CFLAGS="${extra_cflags}" + + CFLAGS_DEBUG=-g + CFLAGS_OPTIMIZE="-O2 -fomit-frame-pointer" + CFLAGS_WARNING="-Wall -Wextra -Wshadow -Wundef -Wwrite-strings -Wpointer-arith" + LDFLAGS_DEBUG= + LDFLAGS_OPTIMIZE= + + case "${CC}" in + *++) + CFLAGS_WARNING="${CFLAGS_WARNING} -Wno-format" + ;; + *) + CFLAGS_WARNING="${CFLAGS_WARNING} -Wc++-compat -fextended-identifiers" + ;; + esac + + # Specify the CC output file names based on the target name + CC_OBJNAME="-o \$@" + CC_EXENAME="-o \$@" + + # Specify linker flags depending on the type of app being + # built -- Console vs. Window. + # + # ORIGINAL COMMENT: + # We need to pass -e _WinMain@16 so that ld will use + # WinMain() instead of main() as the entry point. We can't + # use autoconf to check for this case since it would need + # to run an executable and that does not work when + # cross compiling. Remove this -e workaround once we + # require a gcc that does not have this bug. + # + # MK NOTE: Tk should use a different mechanism. This causes + # interesting problems, such as wish dying at startup. + #LDFLAGS_WINDOW="-mwindows -e _WinMain@16 ${extra_ldflags}" + LDFLAGS_CONSOLE="-mconsole ${extra_ldflags}" + LDFLAGS_WINDOW="-mwindows ${extra_ldflags}" + + case "$do64bit" in + amd64|x64|yes) + MACHINE="AMD64" ; # assume AMD64 as default 64-bit build + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: Using 64-bit $MACHINE mode" >&5 +printf '%s\n' " Using 64-bit $MACHINE mode" >&6; } + ;; + arm64|aarch64) + MACHINE="ARM64" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: Using ARM64 $MACHINE mode" >&5 +printf '%s\n' " Using ARM64 $MACHINE mode" >&6; } + ;; + ia64) + MACHINE="IA64" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: Using IA64 $MACHINE mode" >&5 +printf '%s\n' " Using IA64 $MACHINE mode" >&6; } + ;; + *) + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #ifndef _WIN64 + #error 32-bit + #endif + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_win_64bit=yes +else case e in #( + e) tcl_win_64bit=no + ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + if test "$tcl_win_64bit" = "yes" ; then + do64bit=amd64 + MACHINE="AMD64" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: Using 64-bit $MACHINE mode" >&5 +printf '%s\n' " Using 64-bit $MACHINE mode" >&6; } + fi + ;; + esac + else + if test "${SHARED_BUILD}" = "0" ; then + # static + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: using static flags" >&5 +printf '%s\n' "using static flags" >&6; } + runtime=-MT + LIBRARIES="\${STATIC_LIBRARIES}" + EXESUFFIX="s.exe" + else + # dynamic + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: using shared flags" >&5 +printf '%s\n' "using shared flags" >&6; } + runtime=-MD + # Add SHLIB_LD_LIBS to the Make rule, not here. + LIBRARIES="\${SHARED_LIBRARIES}" + EXESUFFIX=".exe" + case "x`echo \${VisualStudioVersion}`" in + x1[4-9]*) + lflags="${lflags} -nodefaultlib:ucrt.lib" + ;; + *) + ;; + esac + fi + MAKE_DLL="\${SHLIB_LD} \$(LDFLAGS) -out:\$@" + # DLLSUFFIX is separate because it is the building block for + # users of tclConfig.sh that may build shared or static. + DLLSUFFIX=".dll" + LIBSUFFIX=".lib" + LIBFLAGSUFFIX="" + + if test "$do64bit" != "no" ; then + case "$do64bit" in + amd64|x64|yes) + MACHINE="AMD64" ; # assume AMD64 as default 64-bit build + ;; + arm64|aarch64) + MACHINE="ARM64" + ;; + ia64) + MACHINE="IA64" + ;; + esac + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: Using 64-bit $MACHINE mode" >&5 +printf '%s\n' " Using 64-bit $MACHINE mode" >&6; } + fi + + LIBS="netapi32.lib kernel32.lib user32.lib advapi32.lib userenv.lib ws2_32.lib" + + case "x`echo \${VisualStudioVersion}`" in + x1[4-9]*) + LIBS="$LIBS ucrt.lib" + ;; + *) + ;; + esac + + if test "$do64bit" != "no" ; then + RC="rc" + CFLAGS_DEBUG="-nologo -Zi -Od ${runtime}d" + CFLAGS_OPTIMIZE="-nologo -O2 ${runtime}" + lflags="${lflags} -nologo -MACHINE:${MACHINE}" + LINKBIN="link" + # Avoid 'unresolved external symbol __security_cookie' errors. + # c.f. http://support.microsoft.com/?id=894573 + LIBS="$LIBS bufferoverflowU.lib" + else + RC="rc" + # -Od - no optimization + # -WX - warnings as errors + CFLAGS_DEBUG="-nologo -Z7 -Od -WX ${runtime}d" + # -O2 - create fast code (/Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) + CFLAGS_OPTIMIZE="-nologo -O2 ${runtime}" + lflags="${lflags} -nologo" + LINKBIN="link" + fi + + LIBS_GUI="gdi32.lib comdlg32.lib imm32.lib comctl32.lib shell32.lib uuid.lib winspool.lib" + + SHLIB_LD="${LINKBIN} -dll -incremental:no ${lflags}" + SHLIB_LD_LIBS='${LIBS}' + # link -lib only works when -lib is the first arg + STLIB_LD="${LINKBIN} -lib ${lflags}" + RC_OUT=-fo + RC_TYPE=-r + RC_INCLUDE=-i + RC_DEFINE=-d + RES=res + MAKE_LIB="\${STLIB_LD} -out:\$@" + MAKE_STUB_LIB="\${STLIB_LD} -nodefaultlib -out:\$@" + POST_MAKE_LIB= + MAKE_EXE="\${CC} -Fe\$@" + LIBPREFIX="" + + CFLAGS_DEBUG="${CFLAGS_DEBUG} -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE" + CFLAGS_OPTIMIZE="${CFLAGS_OPTIMIZE} -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE" + + EXTRA_CFLAGS="" + CFLAGS_WARNING="-W3" + LDFLAGS_DEBUG="-debug" + LDFLAGS_OPTIMIZE="-release" + + # Specify the CC output file names based on the target name + CC_OBJNAME="-Fo\$@" + CC_EXENAME="-Fe\"\$(shell \$(CYGPATH) '\$@')\"" + + # Specify linker flags depending on the type of app being + # built -- Console vs. Window. + if test "${TARGETCPU}" != "X86"; then + LDFLAGS_CONSOLE="-link ${lflags}" + LDFLAGS_WINDOW=${LDFLAGS_CONSOLE} + else + LDFLAGS_CONSOLE="-link -subsystem:console ${lflags}" + LDFLAGS_WINDOW="-link -subsystem:windows ${lflags}" + fi + fi + + if test "$do64bit" != "no" ; then + printf '%s\n' "#define TCL_CFG_DO64BIT 1" >>confdefs.h + + fi + + if test "${GCC}" = "yes" ; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for SEH support in compiler" >&5 +printf %s "checking for SEH support in compiler... " >&6; } +if test ${tcl_cv_seh+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test "$cross_compiling" = yes +then : + tcl_cv_seh=no +else case e in #( + e) +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to run conftest.$ac_ext, and return whether this succeeded. Assumes that +# executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf '%s\n' "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + printf '%s\n' "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; } +then : + ac_retval=0 +else case e in #( + e) printf '%s\n' "$as_me: program exited with status $ac_status" >&5 + printf '%s\n' "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status ;; +esac +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + #define WIN32_LEAN_AND_MEAN + #include + #undef WIN32_LEAN_AND_MEAN + + int main(int argc, char** argv) { + int a, b = 0; + __try { + a = 666 / b; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + return 0; + } + return 1; + } + +_ACEOF +if ac_fn_c_try_run "$LINENO" +then : + tcl_cv_seh=yes +else case e in #( + e) tcl_cv_seh=no ;; +esac +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi + + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_seh" >&5 +printf '%s\n' "$tcl_cv_seh" >&6; } + if test "$tcl_cv_seh" = "no" ; then + +printf '%s\n' "#define HAVE_NO_SEH 1" >>confdefs.h + + fi + + # + # Check to see if the excpt.h include file provided contains the + # definition for EXCEPTION_DISPOSITION; if not, which is the case + # with Cygwin's version as of 2002-04-10, define it to be int, + # sufficient for getting the current code to work. + # + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for EXCEPTION_DISPOSITION support in include files" >&5 +printf %s "checking for EXCEPTION_DISPOSITION support in include files... " >&6; } +if test ${tcl_cv_eh_disposition+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +# define WIN32_LEAN_AND_MEAN +# include +# undef WIN32_LEAN_AND_MEAN + +int +main (void) +{ + + EXCEPTION_DISPOSITION x; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_eh_disposition=yes +else case e in #( + e) tcl_cv_eh_disposition=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_eh_disposition" >&5 +printf '%s\n' "$tcl_cv_eh_disposition" >&6; } + if test "$tcl_cv_eh_disposition" = "no" ; then + +printf '%s\n' "#define EXCEPTION_DISPOSITION int" >>confdefs.h + + fi + + # See if the compiler supports casting to a union type. + # This is used to stop gcc from printing a compiler + # warning when initializing a union member. + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for cast to union support" >&5 +printf %s "checking for cast to union support... " >&6; } +if test ${tcl_cv_cast_to_union+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + union foo { int i; double d; }; + union foo f = (union foo) (int) 0; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_cast_to_union=yes +else case e in #( + e) tcl_cv_cast_to_union=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cast_to_union" >&5 +printf '%s\n' "$tcl_cv_cast_to_union" >&6; } + if test "$tcl_cv_cast_to_union" = "yes"; then + +printf '%s\n' "#define HAVE_CAST_TO_UNION 1" >>confdefs.h + + fi + fi + + # DL_LIBS is empty, but then we match the Unix version + + + + + + + +# 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 +#------------------------------------------------------------------------ + +if test "${enable_shared+set}" = "set" +then : + + enableval="$enable_shared" + tcl_ok=$enableval + +else case e in #( + e) + tcl_ok=yes + ;; +esac +fi +zlib_lib_name=zdll.lib +tommath_lib_name=tommath.lib +if test "$tcl_ok" = "yes" +then : + + ZLIB_DLL_FILE=\${ZLIB_DLL_FILE} + + TOMMATH_DLL_FILE=\${TOMMATH_DLL_FILE} + + +printf '%s\n' "#define TCL_WITH_EXTERNAL_TOMMATH 1" >>confdefs.h + + if test "$do64bit" != "no" +then : + + +printf '%s\n' "#define MP_64BIT 1" >>confdefs.h + + if test "$do64bit" = "arm64" -o "$do64bit" = "aarch64" +then : + + if test "$GCC" = "yes" +then : + + ZLIB_LIBS=\${ZLIB_DIR_NATIVE}/win64-arm/libz.dll.a + + TOMMATH_LIBS=\${TOMMATH_DIR_NATIVE}/win64-arm/libtommath.dll.a + + zlib_lib_name=libz.dll.a + tommath_lib_name=libtommath.dll.a + +else case e in #( + e) + ZLIB_LIBS=\${ZLIB_DIR_NATIVE}/win64-arm/zdll.lib + + TOMMATH_LIBS=\${TOMMATH_DIR_NATIVE}/win64-arm/tommath.lib + + ;; +esac +fi + +else case e in #( + e) + if test "$GCC" = "yes" +then : + + ZLIB_LIBS=\${ZLIB_DIR_NATIVE}/win64/libz.dll.a + + TOMMATH_LIBS=\${TOMMATH_DIR_NATIVE}/win64/libtommath.dll.a + + zlib_lib_name=libz.dll.a + tommath_lib_name=libtommath.dll.a + +else case e in #( + e) + ZLIB_LIBS=\${ZLIB_DIR_NATIVE}/win64/zdll.lib + + TOMMATH_LIBS=\${TOMMATH_DIR_NATIVE}/win64/tommath.lib + + ;; +esac +fi + ;; +esac +fi + +else case e in #( + e) + ZLIB_LIBS=\${ZLIB_DIR_NATIVE}/win32/zdll.lib + + TOMMATH_LIBS=\${TOMMATH_DIR_NATIVE}/win32/tommath.lib + + ;; +esac +fi + +else case e in #( + e) + +printf '%s\n' "#define TCL_WITH_INTERNAL_ZLIB 1" >>confdefs.h + + ZLIB_OBJS=\${ZLIB_OBJS} + + TOMMATH_OBJS=\${TOMMATH_OBJS} + + ;; +esac +fi +TCL_ZLIB_LIB_NAME=$zlib_lib_name + +TCL_TOMMATH_LIB_NAME=$tommath_lib_name + +ac_fn_c_check_type "$LINENO" "intptr_t" "ac_cv_type_intptr_t" " +#include + +" +if test "x$ac_cv_type_intptr_t" = xyes +then : + +printf '%s\n' "#define HAVE_INTPTR_T 1" >>confdefs.h + + +fi +ac_fn_c_check_type "$LINENO" "uintptr_t" "ac_cv_type_uintptr_t" " +#include + +" +if test "x$ac_cv_type_uintptr_t" = xyes +then : + +printf '%s\n' "#define HAVE_UINTPTR_T 1" >>confdefs.h + + +fi + + +#-------------------------------------------------------------------- +# Zipfs support - Tip 430 +#-------------------------------------------------------------------- +# Check whether --enable-zipfs was given. +if test ${enable_zipfs+y} +then : + enableval=$enable_zipfs; tcl_ok=$enableval +else case e in #( + e) tcl_ok=yes ;; +esac +fi + +if test "$tcl_ok" = "yes" ; then + # + # Find a native compiler + # + # Put a plausible default for CC_FOR_BUILD in Makefile. +if test -z "$CC_FOR_BUILD"; then + if test "x$cross_compiling" = "xno"; then + CC_FOR_BUILD='$(CC)' + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for gcc" >&5 +printf %s "checking for gcc... " >&6; } + if test ${ac_cv_path_cc+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/gcc 2> /dev/null` \ + `ls -r $dir/gcc 2> /dev/null` ; do + if test x"$ac_cv_path_cc" = x ; then + if test -f "$j" ; then + ac_cv_path_cc=$j + break + fi + fi + done + done + ;; +esac +fi + + fi +fi + +# Also set EXEEXT_FOR_BUILD. +if test "x$cross_compiling" = "xno"; then + EXEEXT_FOR_BUILD='$(EXEEXT)' + OBJEXT_FOR_BUILD='$(OBJEXT)' +else + OBJEXT_FOR_BUILD='.no' + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for build system executable suffix" >&5 +printf %s "checking for build system executable suffix... " >&6; } +if test ${bfd_cv_build_exeext+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) rm -f conftest* + echo 'int main () { return 0; }' > conftest.c + bfd_cv_build_exeext= + ${CC_FOR_BUILD} -o conftest conftest.c 1>&5 2>&5 + for file in conftest.*; do + case $file in + *.c | *.o | *.obj | *.ilk | *.pdb) ;; + *) bfd_cv_build_exeext=`echo $file | sed -e s/conftest//` ;; + esac + done + rm -f conftest* + test x"${bfd_cv_build_exeext}" = x && bfd_cv_build_exeext=no ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $bfd_cv_build_exeext" >&5 +printf '%s\n' "$bfd_cv_build_exeext" >&6; } + EXEEXT_FOR_BUILD="" + test x"${bfd_cv_build_exeext}" != xno && EXEEXT_FOR_BUILD=${bfd_cv_build_exeext} +fi + + # + # Find a native zip implementation + # + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for tclsh" >&5 +printf %s "checking for tclsh... " >&6; } + + if test ${ac_cv_path_tclsh+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/tclsh[8-9]*.exe 2> /dev/null` \ + `ls -r $dir/tclsh* 2> /dev/null` ; do + if test x"$ac_cv_path_tclsh" = x ; then + if test -f "$j" ; then + ac_cv_path_tclsh=$j + break + fi + fi + done + done + ;; +esac +fi + + + if test -f "$ac_cv_path_tclsh" ; then + TCLSH_PROG="$ac_cv_path_tclsh" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $TCLSH_PROG" >&5 +printf '%s\n' "$TCLSH_PROG" >&6; } + else + # It is not an error if an installed version of Tcl can't be located. + TCLSH_PROG="" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: No tclsh found on PATH" >&5 +printf '%s\n' "No tclsh found on PATH" >&6; } + fi + + + + ZIP_PROG="" + ZIP_PROG_OPTIONS="" + ZIP_PROG_VFSSEARCH="" + ZIP_INSTALL_OBJS="" + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for zip" >&5 +printf %s "checking for zip... " >&6; } + if test ${ac_cv_path_zip+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/zip 2> /dev/null` \ + `ls -r $dir/zip 2> /dev/null` ; do + if test x"$ac_cv_path_zip" = x ; then + if test -f "$j" ; then + ac_cv_path_zip=$j + break + fi + fi + done + done + ;; +esac +fi + + if test -f "$ac_cv_path_zip" ; then + ZIP_PROG="$ac_cv_path_zip" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ZIP_PROG" >&5 +printf '%s\n' "$ZIP_PROG" >&6; } + ZIP_PROG_OPTIONS="-rq" + ZIP_PROG_VFSSEARCH="*" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: Found INFO Zip in environment" >&5 +printf '%s\n' "Found INFO Zip in environment" >&6; } + # Use standard arguments for zip + else + # It is not an error if an installed version of Zip can't be located. + # We can use the locally distributed minizip instead + ZIP_PROG="./minizip${EXEEXT_FOR_BUILD}" + ZIP_PROG_OPTIONS="-o -r" + ZIP_PROG_VFSSEARCH="*" + ZIP_INSTALL_OBJS="minizip${EXEEXT_FOR_BUILD}" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: No zip found on PATH building minizip" >&5 +printf '%s\n' "No zip found on PATH building minizip" >&6; } + fi + + + + + + 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 +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for building with zipfs" >&5 +printf %s "checking for building with zipfs... " >&6; } +if test "${ZIPFS_BUILD}" = 1; then + if test "${SHARED_BUILD}" = 0; then + ZIPFS_BUILD=2; + +printf '%s\n' "#define ZIPFS_BUILD 2" >>confdefs.h + + else + +printf '%s\n' "#define ZIPFS_BUILD 1" >>confdefs.h +\ + fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf '%s\n' "yes" >&6; } +else +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } +INSTALL_LIBRARIES=install-libraries +INSTALL_MSGS=install-msgs +fi + + + + + + +#-------------------------------------------------------------------- +# 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. + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for FINDEX_INFO_LEVELS in winbase.h" >&5 +printf %s "checking for FINDEX_INFO_LEVELS in winbase.h... " >&6; } +if test ${tcl_cv_findex_enums+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN + +int +main (void) +{ + + FINDEX_INFO_LEVELS i; + FINDEX_SEARCH_OPS j; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_findex_enums=yes +else case e in #( + e) tcl_cv_findex_enums=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_findex_enums" >&5 +printf '%s\n' "$tcl_cv_findex_enums" >&6; } +if test "$tcl_cv_findex_enums" = "no"; then + +printf '%s\n' "#define HAVE_NO_FINDEX_ENUMS 1" >>confdefs.h + +fi + +# See if the compiler supports intrinsics. + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for intrinsics support in compiler" >&5 +printf %s "checking for intrinsics support in compiler... " >&6; } +if test ${tcl_cv_intrinsics+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#include + +int +main (void) +{ + + __cpuidex(0,0,0); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + tcl_cv_intrinsics=yes +else case e in #( + e) tcl_cv_intrinsics=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_intrinsics" >&5 +printf '%s\n' "$tcl_cv_intrinsics" >&6; } +if test "$tcl_cv_intrinsics" = "yes"; then + +printf '%s\n' "#define HAVE_INTRIN_H 1" >>confdefs.h + +fi + +# See if the compiler supports cpuid header. + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for cpuid.h" >&5 +printf %s "checking for cpuid.h... " >&6; } +if test ${tcl_cv_cpuid_h+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include + +int +main (void) +{ + + __get_cpuid(0, 0, 0, 0, 0); + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_cpuid_h=yes +else case e in #( + e) tcl_cv_cpuid_h=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_cpuid_h" >&5 +printf '%s\n' "$tcl_cv_cpuid_h" >&6; } +if test "$tcl_cv_cpuid_h" = "yes"; then + +printf '%s\n' "#define HAVE_CPUID_H 1" >>confdefs.h + +fi + +# See if the header file is present + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for wspiapi.h" >&5 +printf %s "checking for wspiapi.h... " >&6; } +if test ${tcl_cv_wspiapi_h+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_wspiapi_h=yes +else case e in #( + e) tcl_cv_wspiapi_h=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_wspiapi_h" >&5 +printf '%s\n' "$tcl_cv_wspiapi_h" >&6; } +if test "$tcl_cv_wspiapi_h" = "yes"; then + +printf '%s\n' "#define HAVE_WSPIAPI_H 1" >>confdefs.h + +fi + +# See if declarations like FINDEX_INFO_LEVELS are +# missing from winbase.h. This is known to be +# a problem with VC++ 5.2. + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for FINDEX_INFO_LEVELS in winbase.h" >&5 +printf %s "checking for FINDEX_INFO_LEVELS in winbase.h... " >&6; } +if test ${tcl_cv_findex_enums+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN + +int +main (void) +{ + + FINDEX_INFO_LEVELS i; + FINDEX_SEARCH_OPS j; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + tcl_cv_findex_enums=yes +else case e in #( + e) tcl_cv_findex_enums=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $tcl_cv_findex_enums" >&5 +printf '%s\n' "$tcl_cv_findex_enums" >&6; } +if test "$tcl_cv_findex_enums" = "no"; then + +printf '%s\n' "#define HAVE_NO_FINDEX_ENUMS 1" >>confdefs.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. +#-------------------------------------------------------------------- + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for build with symbols" >&5 +printf %s "checking for build with symbols... " >&6; } + # Check whether --enable-symbols was given. +if test ${enable_symbols+y} +then : + enableval=$enable_symbols; tcl_ok=$enableval +else case e in #( + e) tcl_ok=no ;; +esac +fi + +# FIXME: Currently, LDFLAGS_DEFAULT is not used, it should work like CFLAGS_DEFAULT. + if test "$tcl_ok" = "no"; then + CFLAGS_DEFAULT='$(CFLAGS_OPTIMIZE)' + LDFLAGS_DEFAULT='$(LDFLAGS_OPTIMIZE)' + +printf '%s\n' "#define NDEBUG 1" >>confdefs.h + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf '%s\n' "no" >&6; } + + printf '%s\n' "#define TCL_CFG_OPTIMIZED 1" >>confdefs.h + + else + CFLAGS_DEFAULT='$(CFLAGS_DEBUG)' + LDFLAGS_DEFAULT='$(LDFLAGS_DEBUG)' + if test "$tcl_ok" = "yes"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: yes (standard debugging)" >&5 +printf '%s\n' "yes (standard debugging)" >&6; } + fi + fi + + + + if test "$tcl_ok" = "mem" -o "$tcl_ok" = "all"; then + +printf '%s\n' "#define TCL_MEM_DEBUG 1" >>confdefs.h + + fi + + if test "$tcl_ok" = "compile" -o "$tcl_ok" = "all"; then + +printf '%s\n' "#define TCL_COMPILE_DEBUG 1" >>confdefs.h + + +printf '%s\n' "#define TCL_COMPILE_STATS 1" >>confdefs.h + + fi + + if test "$tcl_ok" != "yes" -a "$tcl_ok" != "no"; then + if test "$tcl_ok" = "all"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: enabled symbols mem compile debugging" >&5 +printf '%s\n' "enabled symbols mem compile debugging" >&6; } + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: enabled $tcl_ok debugging" >&5 +printf '%s\n' "enabled $tcl_ok debugging" >&6; } + fi + fi + + +#-------------------------------------------------------------------- +# Embed the manifest if we can determine how +#-------------------------------------------------------------------- + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +printf %s "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if test ${ac_cv_prog_CPP+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) # Double quotes because $CC needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" cpp /lib/cpp + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO" +then : + +else case e in #( + e) # Broken: fails on valid input. +continue ;; +esac +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO" +then : + # Broken: success on invalid input. +continue +else case e in #( + e) # Passes both tests. +ac_preproc_ok=: +break ;; +esac +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok +then : + break +fi + + done + ac_cv_prog_CPP=$CPP + ;; +esac +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +printf '%s\n' "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO" +then : + +else case e in #( + e) # Broken: fails on valid input. +continue ;; +esac +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO" +then : + # Broken: success on invalid input. +continue +else case e in #( + e) # Passes both tests. +ac_preproc_ok=: +break ;; +esac +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok +then : + +else case e in #( + e) { { printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf '%s\n' "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See 'config.log' for more details" "$LINENO" 5; } ;; +esac +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking for egrep -e" >&5 +printf %s "checking for egrep -e... " >&6; } +if test ${ac_cv_path_EGREP_TRADITIONAL+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -z "$EGREP_TRADITIONAL"; then + ac_path_EGREP_TRADITIONAL_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_prog in grep ggrep + do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue +# Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. + # Check for GNU $ac_path_EGREP_TRADITIONAL +case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( +*GNU*) + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; +#( +*) + ac_count=0 + printf %s 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + printf '%s\n' 'EGREP_TRADITIONAL' >> "conftest.nl" + "$ac_path_EGREP_TRADITIONAL" -E 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" + ac_path_EGREP_TRADITIONAL_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_TRADITIONAL_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then + : + fi +else + ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL +fi + + if test "$ac_cv_path_EGREP_TRADITIONAL" +then : + ac_cv_path_EGREP_TRADITIONAL="$ac_cv_path_EGREP_TRADITIONAL -E" +else case e in #( + e) if test -z "$EGREP_TRADITIONAL"; then + ac_path_EGREP_TRADITIONAL_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_prog in egrep + do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue +# Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. + # Check for GNU $ac_path_EGREP_TRADITIONAL +case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( +*GNU*) + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; +#( +*) + ac_count=0 + printf %s 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + printf '%s\n' 'EGREP_TRADITIONAL' >> "conftest.nl" + "$ac_path_EGREP_TRADITIONAL" 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" + ac_path_EGREP_TRADITIONAL_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_TRADITIONAL_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL +fi + ;; +esac +fi ;; +esac +fi +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP_TRADITIONAL" >&5 +printf '%s\n' "$ac_cv_path_EGREP_TRADITIONAL" >&6; } + EGREP_TRADITIONAL=$ac_cv_path_EGREP_TRADITIONAL + + + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether to embed manifest" >&5 +printf %s "checking whether to embed manifest... " >&6; } + # Check whether --enable-embedded-manifest was given. +if test ${enable_embedded_manifest+y} +then : + enableval=$enable_embedded_manifest; embed_ok=$enableval +else case e in #( + e) embed_ok=yes ;; +esac +fi + + + VC_MANIFEST_EMBED_DLL= + VC_MANIFEST_EMBED_EXE= + result=no + if test "$embed_ok" = "yes" -a "${SHARED_BUILD}" = "1" \ + -a "$GCC" != "yes" ; then + # Add the magic to embed the manifest into the dll/exe + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#if defined(_MSC_VER) && _MSC_VER >= 1400 +print("manifest needed") +#endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP_TRADITIONAL "manifest needed" >/dev/null 2>&1 +then : + + # Could do a CHECK_PROG for mt, but should always be with MSVC8+ + # Could add 'if test -f' check, but manifest should be created + # in this compiler case + # Add in a manifest argument that may be specified + # XXX Needs improvement so that the test for existence accounts + # XXX for a provided (known) manifest + VC_MANIFEST_EMBED_DLL="if test -f \$@.manifest ; then mt.exe -nologo -manifest \$@.manifest -outputresource:\$@\;2 ; fi" + VC_MANIFEST_EMBED_EXE="if test -f \$@.manifest ; then mt.exe -nologo -manifest \$@.manifest -outputresource:\$@\;1 ; fi" + result=yes + if test "x" != x ; then + result="yes ()" + fi + +fi +rm -rf conftest* + + fi + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $result" >&5 +printf '%s\n' "$result" >&6; } + + + + +#------------------------------------------------------------------------ +# 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.`" + +# X86|AMD64|ARM64|IA64 for manifest + + + + + + + + + + + + + + + +# empty on win + + + + + + + + + + + + + + + + +# win/tcl.m4 doesn't set (CFLAGS) + + + + + + + +# win/tcl.m4 doesn't set (LDFLAGS) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# win only + + + + + + + + + + + + + + + +ac_config_files="$ac_config_files Makefile tclConfig.sh tclsh.exe.manifest tcl.pc:../unix/tcl.pc.in" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# 'ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* 'ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +ac_cache_dump | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +printf '%s\n' "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +printf '%s\n' "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[][ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`printf '%s\n' "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +case $CONFIG_STATUS in #( + -*) : + CONFIG_STATUS=./$CONFIG_STATUS ;; #( + */*) : + ;; #( + *) : + CONFIG_STATUS=./$CONFIG_STATUS ;; +esac + +ac_write_fail=0 +ac_clean_CONFIG_STATUS='"$CONFIG_STATUS"' +{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +printf '%s\n' "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >"$CONFIG_STATUS" <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>"$CONFIG_STATUS" <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # contradicts POSIX and common usage. Disable this. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else case e in #( + e) case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac ;; +esac +fi + + + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. +as_nl=' +' +export as_nl +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi + +# The user is always right. +if ${PATH_SEPARATOR+false} :; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as 'sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + printf '%s\n' "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + printf '%s\n' "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + printf '%s\n' "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else case e in #( + e) as_fn_append () + { + eval $1=\$$1\$2 + } ;; +esac +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else case e in #( + e) as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } ;; +esac +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`printf '%s\n' "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated + +# Sed expression to map a string onto a valid variable name. +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated + + +exec 6>&1 +## ------------------------------------- ## +## Main body of "$CONFIG_STATUS" script. ## +## ------------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x "$CONFIG_STATUS" || ac_write_fail=1 + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by tcl $as_me 9.0, which was +generated by GNU Autoconf 2.73. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" + +_ACEOF + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +'$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Report bugs to the package provider." + +_ACEOF +ac_cs_config=`printf '%s\n' "$ac_configure_args" | sed "$ac_safe_unquote"` +ac_cs_config_escaped=`printf '%s\n' "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +ac_cs_config='$ac_cs_config_escaped' +ac_cs_version="\\ +tcl config.status 9.0 +configured by $0, generated by GNU Autoconf 2.73, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2026 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || { + awk '' >"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + printf '%s\n' "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + printf '%s\n' "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`printf '%s\n' "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + printf '%s\n' "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: '$1' +Try '$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \printf '%s\n' "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + printf '%s\n' "$ac_log" +} >&5 + +_ACEOF +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "tclConfig.sh") CONFIG_FILES="$CONFIG_FILES tclConfig.sh" ;; + "tclsh.exe.manifest") CONFIG_FILES="$CONFIG_FILES tclsh.exe.manifest" ;; + "tcl.pc") CONFIG_FILES="$CONFIG_FILES tcl.pc:../unix/tcl.pc.in" ;; + + *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to '$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with './config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | sed -n '$='` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | sed -n '$='` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >"$CONFIG_STATUS" || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain ':'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`printf '%s\n' "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is 'configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + printf '%s\n' "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +printf '%s\n' "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`printf '%s\n' "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +printf '%s\n' X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`printf '%s\n' "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`printf '%s\n' "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +printf '%s\n' "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when '$srcdir' = '.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +printf '%s\n' "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_CONFIG_STATUS= + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + case $CONFIG_STATUS in #( + -*) : + ac_no_opts=-- ;; #( + *) : + ac_no_opts= ;; +esac + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $ac_no_opts "$CONFIG_STATUS" $ac_config_status_args || + ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { printf '%s\n' "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +printf '%s\n' "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + + + diff --git a/vendor/tcl/win/configure.ac b/vendor/tcl/win/configure.ac new file mode 100644 index 00000000..1bd8a3a0 --- /dev/null +++ b/vendor/tcl/win/configure.ac @@ -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 +]]) + +#-------------------------------------------------------------------- +# 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 +#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 +#undef WIN32_LEAN_AND_MEAN +#include +]], [[ + __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 +]], [[ + __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 header file is present + +AC_CACHE_CHECK(for wspiapi.h, + tcl_cv_wspiapi_h, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include +]], [[]])], + [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 +#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: diff --git a/vendor/tcl/win/gitmanifest.in b/vendor/tcl/win/gitmanifest.in new file mode 100644 index 00000000..3e7de847 --- /dev/null +++ b/vendor/tcl/win/gitmanifest.in @@ -0,0 +1 @@ +git- \ No newline at end of file diff --git a/vendor/tcl/win/license.terms b/vendor/tcl/win/license.terms new file mode 100644 index 00000000..d8049cd9 --- /dev/null +++ b/vendor/tcl/win/license.terms @@ -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. diff --git a/vendor/tcl/win/makefile.vc b/vendor/tcl/win/makefile.vc new file mode 100644 index 00000000..4cfc880d --- /dev/null +++ b/vendor/tcl/win/makefile.vc @@ -0,0 +1,1195 @@ +#------------------------------------------------------------- -*- makefile -*- +# +# Microsoft Visual C++ makefile for building Tcl with nmake +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# Copyright (c) 1995-1996 Sun Microsystems, Inc. +# Copyright (c) 1998-2000 Ajuba Solutions. +# Copyright (c) 2001-2005 ActiveState Corporation. +# Copyright (c) 2001-2004 David Gravereaux. +# Copyright (c) 2003-2008 Pat Thoyts. +# Copyright (c) 2017 Ashok P. Nadkarni +#------------------------------------------------------------------------------ + +# General usage: +# nmake [-nologo] -f makefile.vc [TARGET|MACRODEF [TARGET|MACRODEF] [...]] +# +# For MACRODEF, see TIP 477 (https://core.tcl-lang.org/tips/doc/main/tip/477.md) +# or examine Sections 7-9 in rules.vc. +# +# Possible values for TARGET are: +# release -- Builds everything that ships with a release. (default) +# core -- Builds the core [tclXX.(dll|lib)] +# shell -- Builds tclsh and the core. +# dlls -- Just builds the windows extensions. +# all -- Builds everything. +# test -- Builds and runs the test suite. +# tcltest -- Just builds the test shell. +# install -- Installs the built binaries and libraries to $(INSTALLDIR) +# as the root of the install tree. +# tidy/clean/hose -- varying levels of cleaning. +# genstubs -- Rebuilds the Stubs table and support files (dev only). +# depend -- Generates an accurate set of source dependancies for this +# makefile. Helpful to avoid problems when the sources are +# refreshed and you rebuild, but can "overbuild" when common +# headers like tclInt.h just get small changes. +# htmlhelp -- Builds a Windows .chm help file for Tcl and Tk from the +# troff manual pages found in $(ROOT)\doc. You need to +# have installed the HTML Help Compiler package from Microsoft +# to produce the .chm file. +# +# The steps to setup a Visual C++ environment depend on which +# version of Visual Studio and/or the Windows SDK you are building +# against and are not described here. The simplest method is generally +# to start a command shell using one of the short cuts installed by +# Visual Studio/Windows SDK for the appropriate target architecture. +# +# NOTE: For older (Visual C++ 6 or the 2003 SDK), to use the Platform +# SDK (not expressly needed), run setenv.bat after vcvars32.bat +# according to the instructions for it. This can also turn on the +# 64-bit compiler, if your SDK has it. +# +# Basic macros and options usable on the commandline (see rules.vc for more info): +# OPTS=nomsvcrt,noembed,nothreads,pdbs,profile,static,symbols,thrdalloc,unchecked,none +# Sets special options for the core. The default is for none. +# Any combination of the above may be used (comma separated). +# 'none' will over-ride everything to nothing. +# +# noembed = Without this option, the Tcl core library scripts +# are embedded into the executable if "static" is +# specified in OPTS, or into the DLL otherwise. If +# "noembed" is specified, the scripts are not embedded +# but copied to the installation target (as in 8.6). +# nomsvcrt = Affects the static option only to switch it from +# using msvcrt(d) as the C runtime [by default] to +# libcmt(d). This is useful for static embedding +# support. +# none = Overrides all other options to nothing. +# nothreads = Turns off full multithreading support (default on). +# pdbs = Produce separate debug symbol files. +# profile = Adds profiling hooks. Map file is assumed. +# static = Builds a static library of the core instead of a +# dll. The shell will be static (and large), and +# have the dde and registry extensions linked inside. +# symbols = Adds symbols for step debugging. +# thrdalloc = Use the thread allocator (shared global free pool). +# unchecked = Allows a symbols build to not use the debug +# enabled runtime (msvcrt.dll not msvcrtd.dll +# or libcmt.lib not libcmtd.lib). +# +# STATS=compdbg,memdbg,none +# Sets optional memory and bytecode compiler debugging code added +# to the core. The default is for none. Any combination of the +# above may be used (comma separated). 'none' will over-ride +# everything to nothing. +# +# compdbg = Enables byte compilation logging. +# memdbg = Enables the debugging memory allocator. +# +# CHECKS=64bit,fullwarn,nodep,none +# Sets special macros for checking compatibility. +# +# 64bit = Enable 64bit portability warnings (if available) +# fullwarn = Builds with full compiler and link warnings enabled. +# Very verbose. +# nodep = Turns off compatibility macros to ensure the core +# isn't being built with deprecated functions. +# +# MACHINE=(ALPHA|AMD64|ARM64|IA64|IX86) +# Set the machine type used for the compiler, linker, and +# resource compiler. This hook is needed to tell the tools +# when alternate platforms are requested. IX86 is the default +# when not specified. If the CPU environment variable has been +# set (ie: recent Platform SDK) then MACHINE is set from CPU. +# +# TMP_DIR= +# OUT_DIR= +# Hooks to allow the intermediate and output directories to be +# changed. $(OUT_DIR) is assumed to be +# $(BINROOT)\(Release|Debug) based on if symbols are requested. +# $(TMP_DIR) will be $(OUT_DIR)\ by default. +# +# TESTPAT= +# Reads the tests requested to be run from this file. +# +# Examples: +# c:\tcl_src\win\>nmake -f makefile.vc release +# c:\tcl_src\win\>nmake -f makefile.vc test +# c:\tcl_src\win\>nmake -f makefile.vc install INSTALLDIR=c:\progra~1\tcl +# c:\tcl_src\win\>nmake -f makefile.vc release OPTS=pdbs +# c:\tcl_src\win\>nmake -f makefile.vc release OPTS=symbols +# + +# NOTE: +# Before modifying this file, check whether the modification is applicable +# to building extensions as well and if so, modify rules.vc instead. + +# The PROJECT macro is used by rules.vc for generating appropriate +# macros and rules. +PROJECT = tcl + +# Default target to build if no target is specified. If unspecified, the +# rules.vc file will set up "all" as the target. +DEFAULT_BUILD_TARGET = release + +# We have a custom resource file +RCFILE = tcl.rc + +# The rules.vc file does much of the hard work in terms of defining +# the build configuration, macros, output directories etc. +!include "rules.vc" + +# +# The tclsh executable without the embedded libzip. We need this +# separately from tclsh to have dependency and build order work right. +# Ditto for the DLL and tcltest +TCLSHRAW=$(TCLSH:.exe=-raw.exe) +TCLLIBRAW=$(TCLLIB:.dll=-raw.dll) + +# Tcl version info based on macros set up by rules.vc +DOTVERSION = $(TCL_MAJOR_VERSION).$(TCL_MINOR_VERSION) +VERSION = $(TCL_MAJOR_VERSION)$(TCL_MINOR_VERSION) + +# The staticpkg option is not longer supported in Tcl 9.0 +# though extensions may still be using it. If specified together +# with "static", ignore it as that is now the default for +# static build. For non-static builds, no longer supported +# now (was permitted in 8.6) +!if $(TCL_USE_STATIC_PACKAGES) +!if $(STATIC_BUILD) +!message *** NOTE: The "staticpkg" option redundant in 9.0. +!else +!message *** NOTE: The "staticpkg" option ignored for shared library builds. +!endif +!endif + +!if [nmakehlp -f $(OPTS) "noembed"] +!message *** Option noembed specified. Tcl script library will not be appended to the binary. +TCL_EMBED_SCRIPTS = 0 +TCL_TEST_LIBRARY=$(ROOT:\=/)/library +!else +!message *** Tcl script library will be appended to the binary. +TCL_EMBED_SCRIPTS = 1 +TCL_TEST_LIBRARY= +!endif + +# We need versions of various core packages to generate appropriate +# file names during installation. +!if [echo REM = This file is generated from makefile.vc > versions.vc] +!endif +!if [echo PKG_HTTP_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\http\pkgIndex.tcl http >> versions.vc] +!endif +!if [echo PKG_OPT_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\opt\pkgIndex.tcl opt >> versions.vc] +!endif +!if [echo PKG_COOKIEJAR_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\cookiejar\pkgIndex.tcl cookiejar >> versions.vc] +!endif +!if [echo PKG_TCLTEST_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\tcltest\pkgIndex.tcl tcltest >> versions.vc] +!endif +!if [echo PKG_MSGCAT_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\msgcat\pkgIndex.tcl msgcat >> versions.vc] +!endif +!if [echo PKG_PLATFORM_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\platform\pkgIndex.tcl "platform " >> versions.vc] +!endif +!if [echo PKG_SHELL_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\platform\pkgIndex.tcl "platform::shell" >> versions.vc] +!endif +!if [echo PKG_DDE_VER = \>> versions.vc] \ + && [nmakehlp -V ..\library\dde\pkgIndex.tcl "dde " >> versions.vc] +!endif +!if [echo PKG_REG_VER =\>> versions.vc] \ + && [nmakehlp -V ..\library\registry\pkgIndex.tcl "registry " >> versions.vc] +!endif + +!include versions.vc + +DDEDOTVERSION = 1.4 +DDEVERSION = $(DDEDOTVERSION:.=) + +REGDOTVERSION = 1.3 +REGVERSION = $(REGDOTVERSION:.=) + +TCLREGLIBNAME = $(PROJECT)9registry$(REGVERSION)$(SUFX:t=).$(EXT) +TCLREGLIB = $(OUT_DIR)\$(TCLREGLIBNAME) + +TCLDDELIBNAME = $(PROJECT)9dde$(DDEVERSION)$(SUFX:t=).$(EXT) +TCLDDELIB = $(OUT_DIR)\$(TCLDDELIBNAME) + +TCLTEST = $(OUT_DIR)\$(PROJECT)test$(VERSION)$(SUFX:t=).exe +TCLTESTRAW = $(TCLTEST:.exe=-raw.exe) + +TCLSHOBJS = \ + $(TMP_DIR)\tclAppInit.obj \ + $(TMP_DIR)\tclsh.res + +TCLTESTOBJS = \ + $(TMP_DIR)\tclTest.obj \ + $(TMP_DIR)\tclTestObj.obj \ + $(TMP_DIR)\tclTestProcBodyObj.obj \ + $(TMP_DIR)\tclMutexTest.obj \ + $(TMP_DIR)\tclThreadTest.obj \ + $(TMP_DIR)\tclWinTest.obj \ + $(TMP_DIR)\tclTestABSList.obj \ +!if !$(STATIC_BUILD) + $(OUT_DIR)\tommath.lib \ +!endif + $(TMP_DIR)\testMain.obj \ + $(TMP_DIR)\tcltest.res + +COREOBJS = \ + $(TMP_DIR)\regcomp.obj \ + $(TMP_DIR)\regerror.obj \ + $(TMP_DIR)\regexec.obj \ + $(TMP_DIR)\regfree.obj \ + $(TMP_DIR)\tclArithSeries.obj \ + $(TMP_DIR)\tclAlloc.obj \ + $(TMP_DIR)\tclAssembly.obj \ + $(TMP_DIR)\tclAsync.obj \ + $(TMP_DIR)\tclBasic.obj \ + $(TMP_DIR)\tclBinary.obj \ + $(TMP_DIR)\tclCkalloc.obj \ + $(TMP_DIR)\tclClock.obj \ + $(TMP_DIR)\tclClockFmt.obj \ + $(TMP_DIR)\tclCmdAH.obj \ + $(TMP_DIR)\tclCmdIL.obj \ + $(TMP_DIR)\tclCmdMZ.obj \ + $(TMP_DIR)\tclCompCmds.obj \ + $(TMP_DIR)\tclCompCmdsGR.obj \ + $(TMP_DIR)\tclCompCmdsSZ.obj \ + $(TMP_DIR)\tclCompExpr.obj \ + $(TMP_DIR)\tclCompile.obj \ + $(TMP_DIR)\tclConfig.obj \ + $(TMP_DIR)\tclDate.obj \ + $(TMP_DIR)\tclDictObj.obj \ + $(TMP_DIR)\tclDisassemble.obj \ + $(TMP_DIR)\tclEncoding.obj \ + $(TMP_DIR)\tclEnsemble.obj \ + $(TMP_DIR)\tclEnv.obj \ + $(TMP_DIR)\tclEvent.obj \ + $(TMP_DIR)\tclExecute.obj \ + $(TMP_DIR)\tclFCmd.obj \ + $(TMP_DIR)\tclFileName.obj \ + $(TMP_DIR)\tclGet.obj \ + $(TMP_DIR)\tclHash.obj \ + $(TMP_DIR)\tclHistory.obj \ + $(TMP_DIR)\tclIcu.obj \ + $(TMP_DIR)\tclIndexObj.obj \ + $(TMP_DIR)\tclInterp.obj \ + $(TMP_DIR)\tclIO.obj \ + $(TMP_DIR)\tclIOCmd.obj \ + $(TMP_DIR)\tclIOGT.obj \ + $(TMP_DIR)\tclIOSock.obj \ + $(TMP_DIR)\tclIOUtil.obj \ + $(TMP_DIR)\tclIORChan.obj \ + $(TMP_DIR)\tclIORTrans.obj \ + $(TMP_DIR)\tclLink.obj \ + $(TMP_DIR)\tclListObj.obj \ + $(TMP_DIR)\tclLiteral.obj \ + $(TMP_DIR)\tclLoad.obj \ + $(TMP_DIR)\tclMainW.obj \ + $(TMP_DIR)\tclMain.obj \ + $(TMP_DIR)\tclNamesp.obj \ + $(TMP_DIR)\tclNotify.obj \ + $(TMP_DIR)\tclOO.obj \ + $(TMP_DIR)\tclOOBasic.obj \ + $(TMP_DIR)\tclOOCall.obj \ + $(TMP_DIR)\tclOODefineCmds.obj \ + $(TMP_DIR)\tclOOInfo.obj \ + $(TMP_DIR)\tclOOMethod.obj \ + $(TMP_DIR)\tclOOProp.obj \ + $(TMP_DIR)\tclOOStubInit.obj \ + $(TMP_DIR)\tclObj.obj \ + $(TMP_DIR)\tclOptimize.obj \ + $(TMP_DIR)\tclPanic.obj \ + $(TMP_DIR)\tclParse.obj \ + $(TMP_DIR)\tclPathObj.obj \ + $(TMP_DIR)\tclPipe.obj \ + $(TMP_DIR)\tclPkg.obj \ + $(TMP_DIR)\tclPkgConfig.obj \ + $(TMP_DIR)\tclPosixStr.obj \ + $(TMP_DIR)\tclPreserve.obj \ + $(TMP_DIR)\tclProc.obj \ + $(TMP_DIR)\tclProcess.obj \ + $(TMP_DIR)\tclRegexp.obj \ + $(TMP_DIR)\tclResolve.obj \ + $(TMP_DIR)\tclResult.obj \ + $(TMP_DIR)\tclScan.obj \ + $(TMP_DIR)\tclStringObj.obj \ + $(TMP_DIR)\tclStrIdxTree.obj \ + $(TMP_DIR)\tclStrToD.obj \ + $(TMP_DIR)\tclStubInit.obj \ + $(TMP_DIR)\tclThread.obj \ + $(TMP_DIR)\tclThreadAlloc.obj \ + $(TMP_DIR)\tclThreadJoin.obj \ + $(TMP_DIR)\tclThreadStorage.obj \ + $(TMP_DIR)\tclTimer.obj \ + $(TMP_DIR)\tclTomMathInterface.obj \ + $(TMP_DIR)\tclTrace.obj \ + $(TMP_DIR)\tclUtf.obj \ + $(TMP_DIR)\tclUtil.obj \ + $(TMP_DIR)\tclVar.obj \ + $(TMP_DIR)\tclZipfs.obj \ + $(TMP_DIR)\tclZlib.obj + +!if $(STATIC_BUILD) +ZLIBOBJS = \ + $(TMP_DIR)\adler32.obj \ + $(TMP_DIR)\compress.obj \ + $(TMP_DIR)\crc32.obj \ + $(TMP_DIR)\deflate.obj \ + $(TMP_DIR)\infback.obj \ + $(TMP_DIR)\inffast.obj \ + $(TMP_DIR)\inflate.obj \ + $(TMP_DIR)\inftrees.obj \ + $(TMP_DIR)\trees.obj \ + $(TMP_DIR)\uncompr.obj \ + $(TMP_DIR)\zutil.obj +!else +ZLIBOBJS = $(OUT_DIR)\zdll.lib +!endif + +!if $(STATIC_BUILD) +TOMMATHOBJS = \ + $(TMP_DIR)\bn_mp_add.obj \ + $(TMP_DIR)\bn_mp_add_d.obj \ + $(TMP_DIR)\bn_mp_and.obj \ + $(TMP_DIR)\bn_mp_clamp.obj \ + $(TMP_DIR)\bn_mp_clear.obj \ + $(TMP_DIR)\bn_mp_clear_multi.obj \ + $(TMP_DIR)\bn_mp_cmp.obj \ + $(TMP_DIR)\bn_mp_cmp_d.obj \ + $(TMP_DIR)\bn_mp_cmp_mag.obj \ + $(TMP_DIR)\bn_mp_cnt_lsb.obj \ + $(TMP_DIR)\bn_mp_copy.obj \ + $(TMP_DIR)\bn_mp_count_bits.obj \ + $(TMP_DIR)\bn_mp_div.obj \ + $(TMP_DIR)\bn_mp_div_d.obj \ + $(TMP_DIR)\bn_mp_div_2.obj \ + $(TMP_DIR)\bn_mp_div_2d.obj \ + $(TMP_DIR)\bn_s_mp_div_3.obj \ + $(TMP_DIR)\bn_mp_exch.obj \ + $(TMP_DIR)\bn_mp_expt_n.obj \ + $(TMP_DIR)\bn_mp_get_mag_u64.obj \ + $(TMP_DIR)\bn_mp_grow.obj \ + $(TMP_DIR)\bn_mp_init.obj \ + $(TMP_DIR)\bn_mp_init_copy.obj \ + $(TMP_DIR)\bn_mp_init_i64.obj \ + $(TMP_DIR)\bn_mp_init_multi.obj \ + $(TMP_DIR)\bn_mp_init_set.obj \ + $(TMP_DIR)\bn_mp_init_size.obj \ + $(TMP_DIR)\bn_mp_init_u64.obj \ + $(TMP_DIR)\bn_mp_lshd.obj \ + $(TMP_DIR)\bn_mp_mod.obj \ + $(TMP_DIR)\bn_mp_mod_2d.obj \ + $(TMP_DIR)\bn_mp_mul.obj \ + $(TMP_DIR)\bn_mp_mul_2.obj \ + $(TMP_DIR)\bn_mp_mul_2d.obj \ + $(TMP_DIR)\bn_mp_mul_d.obj \ + $(TMP_DIR)\bn_mp_neg.obj \ + $(TMP_DIR)\bn_mp_or.obj \ + $(TMP_DIR)\bn_mp_pack.obj \ + $(TMP_DIR)\bn_mp_pack_count.obj \ + $(TMP_DIR)\bn_mp_radix_size.obj \ + $(TMP_DIR)\bn_mp_radix_smap.obj \ + $(TMP_DIR)\bn_mp_read_radix.obj \ + $(TMP_DIR)\bn_mp_rshd.obj \ + $(TMP_DIR)\bn_mp_set_i64.obj \ + $(TMP_DIR)\bn_mp_set_u64.obj \ + $(TMP_DIR)\bn_mp_shrink.obj \ + $(TMP_DIR)\bn_mp_sqr.obj \ + $(TMP_DIR)\bn_mp_sqrt.obj \ + $(TMP_DIR)\bn_mp_sub.obj \ + $(TMP_DIR)\bn_mp_sub_d.obj \ + $(TMP_DIR)\bn_mp_signed_rsh.obj \ + $(TMP_DIR)\bn_mp_to_ubin.obj \ + $(TMP_DIR)\bn_mp_to_radix.obj \ + $(TMP_DIR)\bn_mp_ubin_size.obj \ + $(TMP_DIR)\bn_mp_unpack.obj \ + $(TMP_DIR)\bn_mp_xor.obj \ + $(TMP_DIR)\bn_mp_zero.obj \ + $(TMP_DIR)\bn_s_mp_add.obj \ + $(TMP_DIR)\bn_s_mp_balance_mul.obj \ + $(TMP_DIR)\bn_s_mp_karatsuba_mul.obj \ + $(TMP_DIR)\bn_s_mp_karatsuba_sqr.obj \ + $(TMP_DIR)\bn_s_mp_mul_digs.obj \ + $(TMP_DIR)\bn_s_mp_mul_digs_fast.obj \ + $(TMP_DIR)\bn_s_mp_reverse.obj \ + $(TMP_DIR)\bn_s_mp_sqr.obj \ + $(TMP_DIR)\bn_s_mp_sqr_fast.obj \ + $(TMP_DIR)\bn_s_mp_sub.obj \ + $(TMP_DIR)\bn_s_mp_toom_sqr.obj \ + $(TMP_DIR)\bn_s_mp_toom_mul.obj +!else +TOMMATHOBJS = $(OUT_DIR)\tommath.lib +!endif + +PLATFORMOBJS = \ + $(TMP_DIR)\tclWin32Dll.obj \ + $(TMP_DIR)\tclWinChan.obj \ + $(TMP_DIR)\tclWinConsole.obj \ + $(TMP_DIR)\tclWinError.obj \ + $(TMP_DIR)\tclWinFCmd.obj \ + $(TMP_DIR)\tclWinFile.obj \ + $(TMP_DIR)\tclWinInit.obj \ + $(TMP_DIR)\tclWinLoad.obj \ + $(TMP_DIR)\tclWinNotify.obj \ + $(TMP_DIR)\tclWinPipe.obj \ + $(TMP_DIR)\tclWinSerial.obj \ + $(TMP_DIR)\tclWinSock.obj \ + $(TMP_DIR)\tclWinThrd.obj \ + $(TMP_DIR)\tclWinTime.obj \ +!if $(STATIC_BUILD) + $(TMP_DIR)\tclWinPanic.obj \ + $(TMP_DIR)\tclWinReg.obj \ + $(TMP_DIR)\tclWinDde.obj \ +!else + $(TMP_DIR)\tcl.res +!endif + +TCLOBJS = $(COREOBJS) $(ZLIBOBJS) $(TOMMATHOBJS) $(PLATFORMOBJS) + +TCLSTUBOBJS = \ + $(TMP_DIR)\tclStubLib.obj \ + $(TMP_DIR)\tclStubCall.obj \ + $(TMP_DIR)\tclStubLibTbl.obj \ + $(TMP_DIR)\tclTomMathStubLib.obj \ + $(TMP_DIR)\tclOOStubLib.obj \ + $(TMP_DIR)\tclWinPanic.obj + +### The following paths CANNOT have spaces in them as they appear on +### the left side of implicit rules. +TOMMATHDIR = $(ROOT)\libtommath +PKGSDIR = $(ROOT)\pkgs + +LIBTCLVFSSUBDIR = libtcl.vfs +LIBTCLVFS = $(OUT_DIR)\$(LIBTCLVFSSUBDIR) + +# Additional include and C macro definitions for the implicit rules +# defined in rules.vc +PRJ_INCLUDES = -I"$(TOMMATHDIR)" +PRJ_DEFINES = /DMP_PREC=4 /Dinline=__inline /D_CRT_SECURE_NO_DEPRECATE /D_CRT_NONSTDC_NO_DEPRECATE /DMP_FIXED_CUTOFFS + +!if $(STATIC_BUILD) +PRJ_DEFINES = $(PRJ_DEFINES) /DTCL_WITH_INTERNAL_ZLIB +!endif + +# Additional Link libraries needed beyond those in rules.vc +PRJ_LIBS = netapi32.lib user32.lib userenv.lib ws2_32.lib + +#--------------------------------------------------------------------- +# TclTest flags +#--------------------------------------------------------------------- + +!if "$(TESTPAT)" != "" +TESTFLAGS = $(TESTFLAGS) -file $(TESTPAT) +!endif + + +#--------------------------------------------------------------------- +# Project specific targets +# There are 4 primary build configurations to consider from the combination +# of static/shared and embed/noembed of the library zip. The targets are +# done in the following order. +# $(TCLLIB) - this is either the core static .lib or the .dll. The target +# build does not embed the library zip in the DLL irrespective +# of the noembed setting. A copy is made as $(TCLLIBRAW) +# as the $(TCLLIB) binary is potentially modified later. +# dlls - these are the registry and dde DLL's or static libraries +# $(TCLSH) - the Tcl shell WITHOUT any embedded zip. This needs $(TCLLIB) +# to be built first as it links against it. A copy is made +# as $(TCLSHRAW) as $(TCLSH) binary may be modified later. +# $(TCLSCRIPTZIP) - the zip file that is to be embedded. Note this also +# ships separately and needs to be built irrespective of the +# whether it is embedded or not. All above targets need to +# be built prior as they are used to build the zip (unlike +# Unix where the external zip program is used.) +# core - this virtual target builds the final release ready Tcl +# library. For shared, embedded builds it appends $(TCLSCRIPTZIP) +# to the $(TCLLIB). For other build configurations, this +# is a no-op. +# shell - this virtual target builds the final release ready tclsh shell. +# For static, embedded builds it appends $(TCLSCRIPTZIP) +# to the $(TCLSH). For other build configurations, this +# is a no-op. +# release - Everything that builds as part of a release +#--------------------------------------------------------------------- + +release: setup libtclzip core dlls shell pkgs +all: setup libtclzip core dlls shell pkgs + +core: setup $(TCLLIB) +!if $(TCL_EMBED_SCRIPTS) && !$(STATIC_BUILD) +core: libtclzip + @$(COPY) /b "$(TCLLIBRAW)"+"$(TCLSCRIPTZIP)" "$(TCLLIB)" +!endif + +shell: setup core $(TCLSH) +!if $(TCL_EMBED_SCRIPTS) && $(STATIC_BUILD) +shell: libtclzip + @$(COPY) /b "$(TCLSHRAW)"+"$(TCLSCRIPTZIP)" "$(TCLSH)" +!endif + +dlls: setup $(TCLREGLIB) $(TCLDDELIB) $(OUT_DIR)\zlib1.dll $(OUT_DIR)\libtommath.dll +libtclzip: $(TCLSCRIPTZIP) + +tcltest: setup core $(TCLTEST) dlls +!if $(TCL_EMBED_SCRIPTS) && $(STATIC_BUILD) +tcltest: libtclzip + @$(COPY) /b "$(TCLTESTRAW)"+"$(TCLSCRIPTZIP)" "$(TCLTEST)" +!endif + +install: install-binaries install-libraries install-docs install-pkgs +!if $(SYMBOLS) +install: install-pdbs +!endif +setup: default-setup + +test: test-core test-pkgs +test-core: tcltest + set TCL_LIBRARY=$(TCL_TEST_LIBRARY) +!if $(STATIC_BUILD) + $(DEBUGGER) $(TCLTEST) "$(ROOT:\=/)/tests/all.tcl" $(TESTFLAGS) -loadfile << + package require registry + package require dde +<< +!else + $(DEBUGGER) $(TCLTEST) "$(ROOT:\=/)/tests/all.tcl" $(TESTFLAGS) -loadfile << + package ifneeded dde 1.4.6 [list load "$(TCLDDELIB:\=/)"] + package ifneeded registry 1.3.7 [list load "$(TCLREGLIB:\=/)"] +<< +!endif + +runtest: setup $(TCLTEST) dlls + set TCL_LIBRARY=$(TCL_TEST_LIBRARY) + $(DEBUGGER) $(TCLTEST) $(SCRIPT) + +runshell: setup core shell dlls + set TCL_LIBRARY=$(TCL_TEST_LIBRARY) + $(DEBUGGER) $(TCLSH) $(SCRIPT) + +!if $(STATIC_BUILD) + +$(TCLLIB): $(TCLOBJS) + $(LIBCMD) @<< +$** +<< + +!else + +$(TCLLIB): $(TCLOBJS) + $(DLLCMD) @<< +$** +<< + $(_VC_MANIFEST_EMBED_DLL) +!if $(TCL_EMBED_SCRIPTS) && !$(STATIC_BUILD) + $(COPY) $@ $(TCLLIBRAW) +!endif + +$(TCLIMPLIB): $(TCLLIB) + +!endif # $(STATIC_BUILD) + +$(TCLSTUBLIB): $(TCLSTUBOBJS) + $(LIBCMD) -nodefaultlib $(TCLSTUBOBJS) + +$(TCLSH): $(TCLSHOBJS) $(TCLSTUBLIB) $(TCLIMPLIB) + $(CONEXECMD) -stack:2300000 $** + copy $(TMP_DIR)\tclsh.exe.manifest $(TCLSH).manifest + $(_VC_MANIFEST_EMBED_EXE) +!if $(TCL_EMBED_SCRIPTS) && $(STATIC_BUILD) + $(COPY) $@ $(TCLSHRAW) +!endif + + +$(TCLTEST): $(TCLTESTOBJS) $(TCLSTUBLIB) $(TCLIMPLIB) + $(CONEXECMD) -stack:2300000 $** + copy $(TMP_DIR)\tclsh.exe.manifest $(TCLTEST).manifest + $(_VC_MANIFEST_EMBED_EXE) +!if $(TCL_EMBED_SCRIPTS) && $(STATIC_BUILD) + $(COPY) $@ $(TCLTESTRAW) +!endif + +!if $(STATIC_BUILD) +$(TCLDDELIB): $(TMP_DIR)\tclWinDde.obj + $(LIBCMD) $** +!else +$(TCLDDELIB): $(TMP_DIR)\tclWinDde.obj $(TCLSTUBLIB) + $(DLLCMD) $** + $(_VC_MANIFEST_EMBED_DLL) +!endif + +!if $(STATIC_BUILD) +$(TCLREGLIB): $(TMP_DIR)\tclWinReg.obj + $(LIBCMD) $** +!else +$(TCLREGLIB): $(TMP_DIR)\tclWinReg.obj $(TCLSTUBLIB) + $(DLLCMD) $** + $(_VC_MANIFEST_EMBED_DLL) +!endif + +!if "$(MACHINE)" == "ARM64" +$(OUT_DIR)\zlib1.dll: $(COMPATDIR)\zlib\win64-arm\zlib1.dll + $(COPY) $(COMPATDIR)\zlib\win64-arm\zlib1.dll $(OUT_DIR)\zlib1.dll +$(OUT_DIR)\zdll.lib: $(COMPATDIR)\zlib\win64-arm\zdll.lib + $(COPY) $(COMPATDIR)\zlib\win64-arm\zdll.lib $(OUT_DIR)\zdll.lib +$(OUT_DIR)\libtommath.dll: $(TOMMATHDIR)\win64-arm\libtommath.dll + $(COPY) $(TOMMATHDIR)\win64-arm\libtommath.dll $(OUT_DIR)\libtommath.dll +$(OUT_DIR)\tommath.lib: $(TOMMATHDIR)\win64-arm\tommath.lib + $(COPY) $(TOMMATHDIR)\win64-arm\tommath.lib $(OUT_DIR)\tommath.lib +!elseif "$(MACHINE)" == "IX86" +$(OUT_DIR)\zlib1.dll: $(COMPATDIR)\zlib\win32\zlib1.dll + $(COPY) $(COMPATDIR)\zlib\win32\zlib1.dll $(OUT_DIR)\zlib1.dll +$(OUT_DIR)\zdll.lib: $(COMPATDIR)\zlib\win32\zdll.lib + $(COPY) $(COMPATDIR)\zlib\win32\zdll.lib $(OUT_DIR)\zdll.lib +$(OUT_DIR)\libtommath.dll: $(TOMMATHDIR)\win32\libtommath.dll + $(COPY) $(TOMMATHDIR)\win32\libtommath.dll $(OUT_DIR)\libtommath.dll +$(OUT_DIR)\tommath.lib: $(TOMMATHDIR)\win32\tommath.lib + $(COPY) $(TOMMATHDIR)\win32\tommath.lib $(OUT_DIR)\tommath.lib +!else +$(OUT_DIR)\zlib1.dll: $(COMPATDIR)\zlib\win64\zlib1.dll + $(COPY) $(COMPATDIR)\zlib\win64\zlib1.dll $(OUT_DIR)\zlib1.dll +$(OUT_DIR)\zdll.lib: $(COMPATDIR)\zlib\win64\zdll.lib + $(COPY) $(COMPATDIR)\zlib\win64\zdll.lib $(OUT_DIR)\zdll.lib +$(OUT_DIR)\libtommath.dll: $(TOMMATHDIR)\win64\libtommath.dll + $(COPY) $(TOMMATHDIR)\win64\libtommath.dll $(OUT_DIR)\libtommath.dll +$(OUT_DIR)\tommath.lib: $(TOMMATHDIR)\win64\tommath.lib + $(COPY) $(TOMMATHDIR)\win64\tommath.lib $(OUT_DIR)\tommath.lib +!endif + +$(TCLSCRIPTZIP): $(TCLLIB) $(TCLSH) dlls + @echo Building Tcl library zip file $(TCLSCRIPTZIP) + @set TCL_LIBRARY=$(ROOT:\=/)/library + @if exist "$(LIBTCLVFS)" $(RMDIR) "$(LIBTCLVFS)" + @$(MKDIR) "$(LIBTCLVFS)" + @$(CPYDIR) $(LIBDIR) "$(LIBTCLVFS)\tcl_library" + @move /y "$(LIBTCLVFS)\tcl_library\manifest.txt" "$(LIBTCLVFS)\tcl_library\pkgIndex.tcl" > NUL +# Remove the registry and dde directories as the DLLS are still external + @del "$(LIBTCLVFS)\tcl_library\registry\pkgIndex.tcl" + @rmdir "$(LIBTCLVFS)\tcl_library\registry" + @del "$(LIBTCLVFS)\tcl_library\dde\pkgIndex.tcl" + @rmdir "$(LIBTCLVFS)\tcl_library\dde" + @echo cd {$(OUT_DIR)} > "$(OUT_DIR)\zipper.tcl" + @echo file delete -force {$(@F)} >> "$(OUT_DIR)\zipper.tcl" + @echo zipfs mkzip {$(@F)} {$(LIBTCLVFSSUBDIR)} {$(LIBTCLVFSSUBDIR)} >> "$(OUT_DIR)\zipper.tcl" + @$(TCLSH_NATIVE) "$(OUT_DIR)/zipper.tcl" + +pkgs: + @for /d %d in ($(PKGSDIR)\*) do \ + @if exist "%~fd\win\makefile.vc" ( \ + pushd "%~fd\win" & \ + $(MAKE) -$(MAKEFLAGS) -f makefile.vc TCLDIR=$(ROOT) &\ + popd \ + ) + +test-pkgs: + @for /d %d in ($(PKGSDIR)\*) do \ + @if exist "%~fd\win\makefile.vc" ( \ + pushd "%~fd\win" & \ + $(MAKE) -$(MAKEFLAGS) -f makefile.vc TCLDIR=$(ROOT) test &\ + popd \ + ) + +install-pkgs: + @for /d %d in ($(PKGSDIR)\*) do \ + @if exist "%~fd\win\makefile.vc" ( \ + pushd "%~fd\win" & \ + $(MAKE) -$(MAKEFLAGS) -f makefile.vc TCLDIR=$(ROOT) install &\ + popd \ + ) + +clean-pkgs: + @for /d %d in ($(PKGSDIR)\*) do \ + @if exist "%~fd\win\makefile.vc" ( \ + pushd "%~fd\win" & \ + $(MAKE) -$(MAKEFLAGS) -f makefile.vc TCLDIR=$(ROOT) clean &\ + popd \ + ) + +hose-pkgs: + @for /d %d in ($(PKGSDIR)\*) do \ + @if exist "%~fd\win\makefile.vc" ( \ + pushd "%~fd\win" & \ + $(MAKE) -$(MAKEFLAGS) -f makefile.vc TCLDIR=$(ROOT) hose &\ + popd \ + ) + +#--------------------------------------------------------------------- +# Regenerate the stubs files. [Development use only] +#--------------------------------------------------------------------- + +genstubs: +!if !exist($(TCLSH)) + @echo Build tclsh first! +!else + $(TCLSH) $(TOOLSDIR:\=/)/genStubs.tcl $(GENERICDIR:\=/) \ + $(GENERICDIR:\=/)/tcl.decls $(GENERICDIR:\=/)/tclInt.decls \ + $(GENERICDIR:\=/)/tclTomMath.decls + $(TCLSH) $(TOOLSDIR:\=/)/genStubs.tcl $(GENERICDIR:\=/) \ + $(GENERICDIR:\=/)/tclOO.decls +!endif + + +#--------------------------------------------------------------------- +# Build the Windows HTML help file. +#--------------------------------------------------------------------- + +!if defined(PROCESSOR_ARCHITECTURE) && "$(PROCESSOR_ARCHITECTURE)" == "AMD64" +HHC="%ProgramFiles(x86)%\HTML Help Workshop\hhc.exe" +!else +HHC="%ProgramFiles%\HTML Help Workshop\hhc.exe" +!endif +HTMLDIR=$(OUT_DIR)\html +HTMLBASE=TclTk$(VERSION) +HHPFILE=$(HTMLDIR)\$(HTMLBASE).hhp +CHMFILE=$(HTMLDIR)\$(HTMLBASE).chm + +htmlhelp: $(CHMFILE) + +htmldocs: $(DOCDIR)\* + @$(TCLSH) $(TOOLSDIR)\tcltk-man2html.tcl "--htmldir=$(HTMLDIR)" + +$(CHMFILE): htmldocs chmsetup + @echo Compiling HTML help project + -"$(HHC)" <<$(HHPFILE) >NUL +[OPTIONS] +Compatibility=1.1 or later +Compiled file=$(HTMLBASE).chm +Default topic=index.html +Display compile progress=no +Error log file=$(HTMLBASE).log +Full-text search=Yes +Language=0x409 English (United States) +Title=Tcl/Tk $(DOTVERSION) Help +[FILES] +index.html +docs.css +Keywords\*.html +TclCmd\*.html +TclLib\*.html +TkCmd\*.html +TkLib\*.html +UserCmd\*.html +<< + +chmsetup: + @if not exist $(HTMLDIR)\nul mkdir $(HTMLDIR) + +install-docs: +!if exist("$(CHMFILE)") + @echo Installing compiled HTML help + @$(CPY) "$(CHMFILE)" "$(DOC_INSTALL_DIR)\" +!endif + +# "emacs font-lock highlighting fix + +#--------------------------------------------------------------------- +# Generate the tcl.nmake file which contains the options used to build +# Tcl itself. This is used when building extensions. +#--------------------------------------------------------------------- +tcl-nmake: $(OUT_DIR)\tcl.nmake +$(OUT_DIR)\tcl.nmake: + @type << >$@ +CORE_MACHINE = $(MACHINE) +CORE_DEBUG = $(DEBUG) +CORE_USE_THREAD_ALLOC = $(USE_THREAD_ALLOC) +<< + +#--------------------------------------------------------------------- +# Build tclConfig.sh for the TEA build system. +#--------------------------------------------------------------------- + +tclConfig: $(OUT_DIR)\tclConfig.sh + +# TBD - is this tclConfig.sh file ever used? The values are incorrect! +$(OUT_DIR)\tclConfig.sh: $(WIN_DIR)\tclConfig.sh.in + @echo Creating tclConfig.sh + @nmakehlp -s << $** >$@ +@TCL_DLL_FILE@ $(TCLLIBNAME) +@TCL_VERSION@ $(DOTVERSION) +@TCL_MAJOR_VERSION@ $(TCL_MAJOR_VERSION) +@TCL_MINOR_VERSION@ $(TCL_MINOR_VERSION) +@TCL_PATCH_LEVEL@ $(TCL_PATCH_LEVEL) +@CC@ $(CC) +@DEFS@ $(pkgcflags) +@CFLAGS_DEBUG@ -nologo -c -W3 -YX -Fp$(TMP_DIR)\ -MDd +@CFLAGS_OPTIMIZE@ -nologo -c -W3 -YX -Fp$(TMP_DIR)\ -MD +@LDFLAGS_DEBUG@ -nologo -machine:$(MACHINE) -debug -debugtype:cv +@LDFLAGS_OPTIMIZE@ -nologo -machine:$(MACHINE) -release -opt:ref -opt:icf,3 +@TCL_LIB_FILE@ $(PROJECT)$(VERSION)$(SUFX).lib +@LIBS@ $(baselibs) $(PRJ_LIBS) +@prefix@ $(_INSTALLDIR) +@exec_prefix@ $(BIN_INSTALL_DIR) +@SHLIB_CFLAGS@ +@STLIB_CFLAGS@ +@CFLAGS_WARNING@ -W3 +@EXTRA_CFLAGS@ -YX +@SHLIB_LD@ $(link32) $(dlllflags) +@STLIB_LD@ $(lib32) -nologo +@SHLIB_LD_LIBS@ $(baselibs) $(PRJ_LIBS) +@SHLIB_SUFFIX@ .dll +@LDFLAGS@ +@LIBOBJS@ +@RANLIB@ +@TCL_LIB_FLAG@ $(PROJECT)$(VERSION)$(SUFX).lib +@TCL_BUILD_LIB_SPEC@ $(OUT_DIR)\$(PROJECT)$(VERSION)$(SUFX).lib +@TCL_LIB_SPEC@ $(LIB_INSTALL_DIR)\$(PROJECT)$(VERSION)$(SUFX).lib +@TCL_INCLUDE_SPEC@ -I$(INCLUDE_INSTALL_DIR) +@TCL_SRC_DIR@ $(ROOT) +@TCL_PACKAGE_PATH@ $(LIB_INSTALL_DIR) +@TCL_STUB_LIB_FILE@ $(TCLSTUBLIBNAME) +@TCL_STUB_LIB_FLAG@ $(TCLSTUBLIBNAME) +@TCL_STUB_LIB_SPEC@ -L$(LIB_INSTALL_DIR) $(TCLSTUBLIBNAME) +@TCL_BUILD_STUB_LIB_SPEC@ -L$(OUT_DIR) $(TCLSTUBLIBNAME) +@TCL_BUILD_STUB_LIB_PATH@ $(TCLSTUBLIB) +@TCL_STUB_LIB_PATH@ $(LIB_INSTALL_DIR)\$(TCLSTUBLIBNAME) +@CFG_TCL_SHARED_LIB_SUFFIX@ $(VERSION)$(SUFX).dll +@CFG_TCL_UNSHARED_LIB_SUFFIX@ $(VERSION)$(SUFX).lib +!if $(STATIC_BUILD) +@TCL_SHARED_BUILD@ 0 +!else +@TCL_SHARED_BUILD@ 1 +!endif +@TCL_ZLIB_LIB_NAME@ zdll.lib +@TCL_TOMMATH_LIB_NAME@ tommath.lib +<< + + +#--------------------------------------------------------------------- +# The following target generates the file generic/tclDate.c +# from the yacc grammar found in generic/tclGetDate.y. This is +# only run by hand as yacc is not available in all environments. +# The name of the .c file is different than the name of the .y file +# so that make doesn't try to automatically regenerate the .c file. +#--------------------------------------------------------------------- + +gendate: + bison --output-file=$(GENERICDIR)/tclDate.c \ + --name-prefix=TclDate \ + $(GENERICDIR)/tclGetDate.y + +#--------------------------------------------------------------------- +# Special case object file targets +#--------------------------------------------------------------------- + +$(TMP_DIR)\testMain.obj: $(WIN_DIR)\tclAppInit.c + $(cc32) $(appcflags) /DTCL_TEST /DUNICODE /D_UNICODE \ + -Fo$@ $? + +$(TMP_DIR)\tclMainW.obj: $(GENERICDIR)\tclMain.c + $(cc32) $(pkgcflags) /DUNICODE /D_UNICODE \ + -Fo$@ $? + +$(ROOT)\manifest.uuid: + copy $(WIN_DIR)\gitmanifest.in $(ROOT)\manifest.uuid + git rev-parse HEAD >>$(ROOT)\manifest.uuid + +$(TMP_DIR)\tclUuid.h: $(ROOT)\manifest.uuid + copy /b $(WIN_DIR)\tclUuid.h.in+$(ROOT)\manifest.uuid $(TMP_DIR)\tclUuid.h + +$(TMP_DIR)\tclEvent.obj: $(GENERICDIR)\tclEvent.c $(TMP_DIR)\tclUuid.h + $(cc32) $(pkgcflags) -I$(COMPATDIR)\zlib -I$(TMP_DIR) \ + -Fo$@ $(GENERICDIR)\tclEvent.c + +$(TMP_DIR)\tclTest.obj: $(GENERICDIR)\tclTest.c $(TMP_DIR)\tclUuid.h + $(cc32) $(appcflags) -I$(TMP_DIR) \ + -Fo$@ $(GENERICDIR)\tclTest.c + +$(TMP_DIR)\tclTestObj.obj: $(GENERICDIR)\tclTestObj.c + $(cc32) $(appcflags) -Fo$@ $? + +$(TMP_DIR)\tclTestABSList.obj: $(GENERICDIR)\tclTestABSList.c + $(cc32) $(appcflags) -Fo$@ $? + +$(TMP_DIR)\tclWinTest.obj: $(WIN_DIR)\tclWinTest.c + $(CCAPPCMD) $? + +$(TMP_DIR)\tclZipfs.obj: $(GENERICDIR)\tclZipfs.c + $(cc32) $(pkgcflags) \ + -I$(COMPATDIR)\zlib -I$(COMPATDIR)\zlib\contrib\minizip \ + -Fo$@ $? + +$(TMP_DIR)\tclZlib.obj: $(GENERICDIR)\tclZlib.c + $(cc32) $(pkgcflags) -I$(COMPATDIR)\zlib -Fo$@ $? + +# Following the lead of the autoconf based make, we define the +# CFG_RUNTIME_*DIR flags specifically for tclPkgConfig +# and not as part of the global defines. These are all defined +# as empty strings because they are intended to represent paths +# at *runtime*, not build time. This may make sense on Unix systems +# where end-user does configure and make on the target system. It +# makes no sense on Windows where binary distributions may be installed +# anywhere. Storing build time paths as runtime paths is misleading +# at best and inefficient at worst as the code goes looking for +# files and directories that do not exist. +# Note: the same is true for the other CFG_RUNTIME* and CFG_INSTALL* +# settings as well but they are historical and I do not want to change +# them. +$(TMP_DIR)\tclPkgConfig.obj: $(GENERICDIR)\tclPkgConfig.c + $(cc32) $(pkgcflags) \ + /DCFG_INSTALL_LIBDIR="\"$(LIB_INSTALL_DIR:\=\\)\"" \ + /DCFG_INSTALL_BINDIR="\"$(BIN_INSTALL_DIR:\=\\)\"" \ + /DCFG_INSTALL_SCRDIR="\"$(SCRIPT_INSTALL_DIR:\=\\)\"" \ + /DCFG_INSTALL_INCDIR="\"$(INCLUDE_INSTALL_DIR:\=\\)\"" \ + /DCFG_INSTALL_DOCDIR="\"$(DOC_INSTALL_DIR:\=\\)\"" \ + /DCFG_RUNTIME_LIBDIR="\"$(LIB_INSTALL_DIR:\=\\)\"" \ + /DCFG_RUNTIME_BINDIR="\"$(BIN_INSTALL_DIR:\=\\)\"" \ + /DCFG_RUNTIME_SCRDIR="\"$(SCRIPT_INSTALL_DIR:\=\\)\"" \ + /DCFG_RUNTIME_INCDIR="\"$(INCLUDE_INSTALL_DIR:\=\\)\"" \ + /DCFG_RUNTIME_DOCDIR="\"$(DOC_INSTALL_DIR:\=\\)\"" \ + /DCFG_RUNTIME_DLLFILE="\"$(TCL_LIB_FILE)\"" \ + -Fo$@ $? + +$(TMP_DIR)\tclAppInit.obj: $(WIN_DIR)\tclAppInit.c + $(cc32) $(appcflags) /DUNICODE /D_UNICODE \ + -Fo$@ $? + +### The following objects should be built using the stub interfaces + +$(TMP_DIR)\tclWinReg.obj: $(WIN_DIR)\tclWinReg.c + $(cc32) $(appcflags_nostubs) /DUSE_TCL_STUBS=1 -Fo$@ $? + + +$(TMP_DIR)\tclWinDde.obj: $(WIN_DIR)\tclWinDde.c + $(cc32) $(appcflags_nostubs) /DUSE_TCL_STUBS=1 -Fo$@ $? + + +### The following objects are part of the stub library and should not +### be built as DLL objects. -Zl is used to avoid a dependency on any +### specific C run-time. + +$(TMP_DIR)\tclStubLib.obj: $(GENERICDIR)\tclStubLib.c + $(cc32) $(stubscflags) -Fo$@ $? + +$(TMP_DIR)\tclStubCall.obj: $(GENERICDIR)\tclStubCall.c + $(cc32) $(stubscflags) \ + /DCFG_RUNTIME_DLLFILE="\"$(TCLLIBNAME)\"" \ + /DCFG_RUNTIME_BINDIR="\"$(BIN_INSTALL_DIR:\=\\)\"" \ + $(TCL_INCLUDES) -Fo$@ $? + +$(TMP_DIR)\tclStubLibTbl.obj: $(GENERICDIR)\tclStubLibTbl.c + $(cc32) $(stubscflags) $(TCL_INCLUDES) -Fo$@ $? + +$(TMP_DIR)\tclTomMathStubLib.obj: $(GENERICDIR)\tclTomMathStubLib.c + $(cc32) $(stubscflags) -Fo$@ $? + +$(TMP_DIR)\tclOOStubLib.obj: $(GENERICDIR)\tclOOStubLib.c + $(cc32) $(stubscflags) -Fo$@ $? + +$(TMP_DIR)\tclWinPanic.obj: $(WIN_DIR)\tclWinPanic.c + $(cc32) $(stubscflags) -Fo$@ $? + +$(TMP_DIR)\tclsh.exe.manifest: $(WIN_DIR)\tclsh.exe.manifest.in + @nmakehlp -s << $** >$@ +@MACHINE@ $(MACHINE:IX86=X86) +@TCL_WIN_VERSION@ $(DOTVERSION).0.0 +<< + +#--------------------------------------------------------------------- +# Generate the source dependencies. Having dependency rules will +# improve incremental build accuracy without having to resort to a +# full rebuild just because some non-global header file like +# tclCompile.h was changed. These rules aren't needed when building +# from scratch. +#--------------------------------------------------------------------- + +depend: +!if !exist($(TCLSH)) + @echo Build tclsh first! +!else + $(TCLSH) $(TOOLSDIR:\=/)/mkdepend.tcl -vc32 -out:"$(OUT_DIR)\depend.mk" \ + -passthru:"/DBUILD_tcl $(TCL_INCLUDES) $(PRJ_INCLUDES)" $(GENERICDIR),$$(GENERICDIR) \ + $(COMPATDIR),$$(COMPATDIR) $(TOMMATHDIR),$$(TOMMATHDIR) $(WIN_DIR),$$(WIN_DIR) @<< +$(TCLOBJS) +<< +!endif + +#--------------------------------------------------------------------- +# Dependency rules +#--------------------------------------------------------------------- + +!if exist("$(OUT_DIR)\depend.mk") +!include "$(OUT_DIR)\depend.mk" +!message *** Dependency rules in use. +!else +!message *** Dependency rules are not being used. +!endif + +### add a spacer in the output +!message + + +#--------------------------------------------------------------------- +# Implicit rules that are not covered by the common ones defined in +# rules.vc. A limitation exists with nmake that requires that +# source directory can not contain spaces in the path. This an +# absolute. +#--------------------------------------------------------------------- + +{$(TOMMATHDIR)}.c{$(TMP_DIR)}.obj:: + $(cc32) $(pkgcflags) -Fo$(TMP_DIR)\ @<< +$< +<< + +{$(COMPATDIR)\zlib}.c{$(TMP_DIR)}.obj:: + $(cc32) $(pkgcflags) -Fo$(TMP_DIR)\ @<< +$< +<< + +$(TMP_DIR)\tclsh.res: $(TMP_DIR)\tclsh.exe.manifest $(WIN_DIR)\tclsh.rc + +$(TMP_DIR)\tcltest.res: $(TMP_DIR)\tclsh.exe.manifest $(WIN_DIR)\tcltest.rc + +#--------------------------------------------------------------------- +# Installation. +#--------------------------------------------------------------------- + +install-binaries: + @echo Installing to '$(_INSTALLDIR)' + @echo Installing $(TCLLIBNAME) +!if "$(TCLLIB)" != "$(TCLIMPLIB)" + @$(CPY) "$(TCLLIB)" "$(BIN_INSTALL_DIR)\" +!endif + @$(CPY) "$(TCLIMPLIB)" "$(LIB_INSTALL_DIR)\" + @$(CPY) "$(OUT_DIR)\zlib1.dll" "$(BIN_INSTALL_DIR)\" + @$(CPY) "$(OUT_DIR)\libtommath.dll" "$(BIN_INSTALL_DIR)\" +!if !$(STATIC_BUILD) + @$(CPY) "$(OUT_DIR)\zdll.lib" "$(LIB_INSTALL_DIR)\" + @$(CPY) "$(OUT_DIR)\tommath.lib" "$(LIB_INSTALL_DIR)\" +!endif + @echo Installing $(TCLSHNAME) + @$(CPY) "$(TCLSH)" "$(BIN_INSTALL_DIR)\" + @echo Installing $(TCLSTUBLIBNAME) + @$(CPY) "$(TCLSTUBLIB)" "$(LIB_INSTALL_DIR)\" + +install-libraries: tclConfig tcl-nmake install-msgs install-tzdata + @if not exist "$(LIB_INSTALL_DIR)\nmake" \ + $(MKDIR) "$(LIB_INSTALL_DIR)\nmake" + @echo Installing header files + @$(CPY) "$(GENERICDIR)\tcl.h" "$(INCLUDE_INSTALL_DIR)\" + @$(CPY) "$(GENERICDIR)\tclDecls.h" "$(INCLUDE_INSTALL_DIR)\" + @$(CPY) "$(GENERICDIR)\tclOO.h" "$(INCLUDE_INSTALL_DIR)\" + @$(CPY) "$(GENERICDIR)\tclOODecls.h" "$(INCLUDE_INSTALL_DIR)\" + @$(CPY) "$(GENERICDIR)\tclPlatDecls.h" "$(INCLUDE_INSTALL_DIR)\" + @$(CPY) "$(GENERICDIR)\tclTomMath.h" "$(INCLUDE_INSTALL_DIR)\" + @$(CPY) "$(GENERICDIR)\tclTomMathDecls.h" "$(INCLUDE_INSTALL_DIR)\" + @$(CPY) "$(TOMMATHDIR)\tommath.h" "$(INCLUDE_INSTALL_DIR)\" +!if !$(TCL_EMBED_SCRIPTS) + @echo Installing library files to $(SCRIPT_INSTALL_DIR) + @if not exist "$(SCRIPT_INSTALL_DIR)" \ + $(MKDIR) "$(SCRIPT_INSTALL_DIR)" + @$(CPY) "$(ROOT)\library\*.tcl" "$(SCRIPT_INSTALL_DIR)\" + @$(CPY) "$(ROOT)\library\tclIndex" "$(SCRIPT_INSTALL_DIR)\" +!endif + @$(CPY) "$(OUT_DIR)\tclConfig.sh" "$(LIB_INSTALL_DIR)\" + @$(CPY) "$(WIN_DIR)\tclooConfig.sh" "$(LIB_INSTALL_DIR)\" + @$(CPY) "$(TCLSCRIPTZIP)" "$(LIB_INSTALL_DIR)\" + @$(CPY) "$(WIN_DIR)\rules.vc" "$(LIB_INSTALL_DIR)\nmake\" + @$(CPY) "$(WIN_DIR)\targets.vc" "$(LIB_INSTALL_DIR)\nmake\" + @$(CPY) "$(WIN_DIR)\nmakehlp.c" "$(LIB_INSTALL_DIR)\nmake\" + @$(CPY) "$(WIN_DIR)\x86_64-w64-mingw32-nmakehlp.exe" "$(LIB_INSTALL_DIR)\nmake\" + @$(CPY) "$(OUT_DIR)\tcl.nmake" "$(LIB_INSTALL_DIR)\nmake\" +!if !$(TCL_EMBED_SCRIPTS) + @echo Installing package cookiejar $(PKG_COOKIEJAR_VER) + @if not exist "$(SCRIPT_INSTALL_DIR)\cookiejar0.2" \ + $(MKDIR) "$(SCRIPT_INSTALL_DIR)\cookiejar0.2" + @$(CPY) "$(ROOT)\library\cookiejar\*.tcl" \ + "$(SCRIPT_INSTALL_DIR)\cookiejar0.2\" + @$(CPY) "$(ROOT)\library\cookiejar\*.gz" \ + "$(SCRIPT_INSTALL_DIR)\cookiejar0.2\" + @echo Installing package opt $(PKG_OPT_VER) + @if not exist "$(SCRIPT_INSTALL_DIR)\opt0.4" \ + $(MKDIR) "$(SCRIPT_INSTALL_DIR)\opt0.4" + @$(CPY) "$(ROOT)\library\opt\*.tcl" \ + "$(SCRIPT_INSTALL_DIR)\opt0.4\" + @if not exist "$(MODULE_INSTALL_DIR)" \ + $(MKDIR) "$(MODULE_INSTALL_DIR)" + @echo Installing package http $(PKG_HTTP_VER) as a Tcl Module + @if not exist "$(MODULE_INSTALL_DIR)\9.0" \ + $(MKDIR) "$(MODULE_INSTALL_DIR)\9.0" + @$(COPY) "$(ROOT)\library\http\http.tcl" \ + "$(MODULE_INSTALL_DIR)\9.0\http-$(PKG_HTTP_VER).tm" + @echo Installing package msgcat $(PKG_MSGCAT_VER) as a Tcl Module + @$(COPY) "$(ROOT)\library\msgcat\msgcat.tcl" \ + "$(MODULE_INSTALL_DIR)\9.0\msgcat-$(PKG_MSGCAT_VER).tm" + @echo Installing package tcltest $(PKG_TCLTEST_VER) as a Tcl Module + @$(COPY) "$(ROOT)\library\tcltest\tcltest.tcl" \ + "$(MODULE_INSTALL_DIR)\9.0\tcltest-$(PKG_TCLTEST_VER).tm" + @echo Installing package platform $(PKG_PLATFORM_VER) as a Tcl Module + @if not exist "$(MODULE_INSTALL_DIR)\9.0\platform" \ + $(MKDIR) "$(MODULE_INSTALL_DIR)\9.0\platform" + @$(COPY) "$(ROOT)\library\platform\platform.tcl" \ + "$(MODULE_INSTALL_DIR)\9.0\platform-$(PKG_PLATFORM_VER).tm" + @echo Installing package platform::shell $(PKG_SHELL_VER) as a Tcl Module + @$(COPY) "$(ROOT)\library\platform\shell.tcl" \ + "$(MODULE_INSTALL_DIR)\9.0\platform\shell-$(PKG_SHELL_VER).tm" +!endif + @echo Installing $(TCLDDELIBNAME) + @$(CPY) "$(TCLDDELIB)" "$(LIB_INSTALL_DIR)\dde$(DDEDOTVERSION)\" + @$(CPY) "$(ROOT)\library\dde\pkgIndex.tcl" \ + "$(LIB_INSTALL_DIR)\dde$(DDEDOTVERSION)\" + @echo Installing $(TCLREGLIBNAME) + @$(CPY) "$(TCLREGLIB)" "$(LIB_INSTALL_DIR)\registry$(REGDOTVERSION)\" + @$(CPY) "$(ROOT)\library\registry\pkgIndex.tcl" \ + "$(LIB_INSTALL_DIR)\registry$(REGDOTVERSION)\" +!if !$(TCL_EMBED_SCRIPTS) + @echo Installing encodings + @$(CPY) "$(ROOT)\library\encoding\*.enc" \ + "$(SCRIPT_INSTALL_DIR)\encoding\" +!endif +# "emacs font-lock highlighting fix + +install-tzdata: +!if !$(TCL_EMBED_SCRIPTS) + @echo Installing time zone data + @set TCL_LIBRARY=$(ROOT:\=/)/library + @$(TCLSH_NATIVE) "$(ROOT:\=/)/tools/installData.tcl" \ + "$(ROOT:\=/)/library/tzdata" "$(SCRIPT_INSTALL_DIR)/tzdata" +!endif + +install-msgs: +!if !$(TCL_EMBED_SCRIPTS) + @echo Installing message catalogs + @set TCL_LIBRARY=$(ROOT:\=/)/library + @$(TCLSH_NATIVE) "$(ROOT:\=/)/tools/installData.tcl" \ + "$(ROOT:\=/)/library/msgs" "$(SCRIPT_INSTALL_DIR)/msgs" +!endif + +install-pdbs: + @echo Installing debug symbols + @$(CPY) "$(OUT_DIR)\*.pdb" "$(BIN_INSTALL_DIR)\" +# "emacs font-lock highlighting fix + +#--------------------------------------------------------------------- +# Clean up +#--------------------------------------------------------------------- + +tidy: +!if "$(TCLLIB)" != "$(TCLIMPLIB)" + @echo Removing $(TCLLIB) ... + @if exist $(TCLLIB) del $(TCLLIB) +!endif + @echo Removing $(TCLIMPLIB) ... + @if exist $(TCLIMPLIB) del $(TCLIMPLIB) + @echo Removing $(TCLSH) ... + @if exist $(TCLSH) del $(TCLSH) + @echo Removing $(TCLTEST) ... + @if exist $(TCLTEST) del $(TCLTEST) + @echo Removing $(TCLDDELIB) ... + @if exist $(TCLDDELIB) del $(TCLDDELIB) + @echo Removing $(TCLREGLIB) ... + @if exist $(TCLREGLIB) del $(TCLREGLIB) + +clean: default-clean clean-pkgs +hose: default-hose hose-pkgs +realclean: hose +.PHONY: + +# Local Variables: +# mode: makefile +# End: diff --git a/vendor/tcl/win/nmakehlp.c b/vendor/tcl/win/nmakehlp.c new file mode 100644 index 00000000..59b070e5 --- /dev/null +++ b/vendor/tcl/win/nmakehlp.c @@ -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 +#ifdef _MSC_VER +#pragma comment (lib, "user32.lib") +#pragma comment (lib, "kernel32.lib") +#endif +#include + +/* + * 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 \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 ? ...?\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 \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 \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= + * 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: + */ diff --git a/vendor/tcl/win/rules-ext.vc b/vendor/tcl/win/rules-ext.vc new file mode 100644 index 00000000..479720a4 --- /dev/null +++ b/vendor/tcl/win/rules-ext.vc @@ -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 \ No newline at end of file diff --git a/vendor/tcl/win/rules.vc b/vendor/tcl/win/rules.vc new file mode 100644 index 00000000..ce5ddd64 --- /dev/null +++ b/vendor/tcl/win/rules.vc @@ -0,0 +1,1919 @@ +#------------------------------------------------------------- -*- makefile -*- +# rules.vc -- +# +# Part of the nmake based build system for Tcl and its extensions. +# This file does all the hard work in terms of parsing build options, +# compiler switches, defining common targets and macros. The Tcl makefile +# directly includes this. Extensions include it via "rules-ext.vc". +# +# See TIP 477 (https://core.tcl-lang.org/tips/doc/main/tip/477.md) for +# detailed documentation. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# Copyright (c) 2001-2003 David Gravereaux. +# Copyright (c) 2003-2008 Patrick Thoyts +# Copyright (c) 2017 Ashok P. Nadkarni +#------------------------------------------------------------------------------ + +!ifndef _RULES_VC +_RULES_VC = 1 + +# The following macros define the version of the rules.vc nmake build system +# For modifications that are not backward-compatible, you *must* change +# the major version. +RULES_VERSION_MAJOR = 1 +RULES_VERSION_MINOR = 16 + +# The PROJECT macro must be defined by parent makefile. +!if "$(PROJECT)" == "" +!error *** Error: Macro PROJECT not defined! Please define it before including rules.vc +!endif + +!if "$(PRJ_PACKAGE_TCLNAME)" == "" +PRJ_PACKAGE_TCLNAME = $(PROJECT) +!endif + +# Also special case Tcl and Tk to save some typing later +DOING_TCL = 0 +DOING_TK = 0 +!if "$(PROJECT)" == "tcl" +DOING_TCL = 1 +!elseif "$(PROJECT)" == "tk" +DOING_TK = 1 +!endif + +!ifndef NEED_TK +# Backwards compatibility +!ifdef PROJECT_REQUIRES_TK +NEED_TK = $(PROJECT_REQUIRES_TK) +!else +NEED_TK = 0 +!endif +!endif + +!ifndef NEED_TCL_SOURCE +NEED_TCL_SOURCE = 0 +!endif + +!ifdef NEED_TK_SOURCE +!if $(NEED_TK_SOURCE) +NEED_TK = 1 +!endif +!else +NEED_TK_SOURCE = 0 +!endif + +################################################################ +# Nmake is a pretty weak environment in syntax and capabilities +# so this file is necessarily verbose. It's broken down into +# the following parts. +# +# 0. Sanity check that compiler environment is set up and initialize +# any built-in settings from the parent makefile +# 1. First define the external tools used for compiling, copying etc. +# as this is independent of everything else. +# 2. Figure out our build structure in terms of the directory, whether +# we are building Tcl or an extension, etc. +# 3. Determine the compiler and linker versions +# 4. Build the nmakehlp helper application +# 5. Determine the supported compiler options and features +# 6. Extract Tcl, Tk, and possibly extensions, version numbers from the +# headers +# 7. Parse the OPTS macro value for user-specified build configuration +# 8. Parse the STATS macro value for statistics instrumentation +# 9. Parse the CHECKS macro for additional compilation checks +# 10. Based on this selected configuration, construct the output +# directory and file paths +# 11. Construct the paths where the package is to be installed +# 12. Set up the actual options passed to compiler and linker based +# on the information gathered above. +# 13. Define some standard build targets and implicit rules. These may +# be optionally disabled by the parent makefile. +# 14. (For extensions only.) Compare the configuration of the target +# Tcl and the extensions and warn against discrepancies. +# +# One final note about the macro names used. They are as they are +# for historical reasons. We would like legacy extensions to +# continue to work with this make include file so be wary of +# changing them for consistency or clarity. + +# 0. Sanity check compiler environment + +# Check to see we are configured to build with MSVC (MSDEVDIR, MSVCDIR or +# VCINSTALLDIR) or with the MS Platform SDK (MSSDK or WindowsSDKDir) + +!if !defined(MSDEVDIR) && !defined(MSVCDIR) && !defined(VCINSTALLDIR) && !defined(MSSDK) && !defined(WINDOWSSDKDIR) +MSG = ^ +Visual C++ compiler environment not initialized. +!error $(MSG) +!endif + +# 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 + + +################################################################ +# 1. Define external programs being used + +#---------------------------------------------------------- +# Set the proper copy method to avoid overwrite questions +# to the user when copying files and selecting the right +# "delete all" method. +#---------------------------------------------------------- +# Tcl Bug be40b736: use rd/md instead rmdir/mkdir. +# Otherwise, an eventual cygwin version with different +# parameters may be used. +#---------------------------------------------------------- + +RMDIR = rd /S /Q +CPY = xcopy /i /y >NUL +CPYDIR = xcopy /e /i /y >NUL +COPY = copy /y >NUL +MKDIR = md + +###################################################################### +# 2. Figure out our build environment in terms of what we're building. +# +# (a) Tcl itself +# (b) Tk +# (c) a Tcl extension using libraries/includes from an *installed* Tcl +# (d) a Tcl extension using libraries/includes from Tcl source directory +# +# This last is needed because some extensions still need +# some Tcl interfaces that are not publicly exposed. +# +# The fragment will set the following macros: +# ROOT - root of this module sources +# COMPATDIR - source directory that holds compatibility sources +# DOCDIR - source directory containing documentation files +# GENERICDIR - platform-independent source directory +# WIN_DIR - Windows-specific source directory +# TESTDIR - directory containing test files +# TOOLSDIR - directory containing build tools +# _TCLDIR - root of the Tcl installation OR the Tcl sources. Not set +# when building Tcl itself. +# _INSTALLDIR - native form of the installation path. For Tcl +# this will be the root of the Tcl installation. For extensions +# this will be the lib directory under the root. +# TCLINSTALL - set to 1 if _TCLDIR refers to +# headers and libraries from an installed Tcl, and 0 if built against +# Tcl sources. Not set when building Tcl itself. Yes, not very well +# named. +# _TCL_H - native path to the tcl.h file +# +# If Tk is involved, also sets the following +# _TKDIR - native form Tk installation OR Tk source. Not set if building +# Tk itself. +# TKINSTALL - set 1 if _TKDIR refers to installed Tk and 0 if Tk sources +# _TK_H - native path to the tk.h file + +# Root directory for sources and assumed subdirectories +ROOT = $(MAKEDIR)\.. +# The following paths CANNOT have spaces in them as they appear on the +# left side of implicit rules. +!ifndef COMPATDIR +COMPATDIR = $(ROOT)\compat +!endif +!ifndef DOCDIR +DOCDIR = $(ROOT)\doc +!endif +!ifndef GENERICDIR +GENERICDIR = $(ROOT)\generic +!endif +!ifndef TOOLSDIR +TOOLSDIR = $(ROOT)\tools +!endif +!ifndef TESTDIR +TESTDIR = $(ROOT)\tests +!endif +!ifndef LIBDIR +!if exist("$(ROOT)\library") +LIBDIR = $(ROOT)\library +!else +LIBDIR = $(ROOT)\lib +!endif +!endif +!ifndef DEMODIR +!if exist("$(LIBDIR)\demos") +DEMODIR = $(LIBDIR)\demos +!else +DEMODIR = $(ROOT)\demos +!endif +!endif # ifndef DEMODIR +# Do NOT use WINDIR because it is Windows internal environment +# variable to point to c:\windows! +WIN_DIR = $(ROOT)\win + +!ifndef RCDIR +!if exist("$(WIN_DIR)\rc") +RCDIR = $(WIN_DIR)\rc +!else +RCDIR = $(WIN_DIR) +!endif +!endif +RCDIR = $(RCDIR:/=\) + +# The target directory where the built packages and binaries will be installed. +# INSTALLDIR is the (optional) path specified by the user. +# _INSTALLDIR is INSTALLDIR using the backslash separator syntax +!ifdef INSTALLDIR +### Fix the path separators. +_INSTALLDIR = $(INSTALLDIR:/=\) +!else +### Assume the normal default. +_INSTALLDIR = $(HOMEDRIVE)\Tcl +!endif + +!if $(DOING_TCL) + +# BEGIN Case 2(a) - Building Tcl itself + +# Only need to define _TCL_H +_TCL_H = ..\generic\tcl.h + +# END Case 2(a) - Building Tcl itself + +!elseif $(DOING_TK) + +# BEGIN Case 2(b) - Building Tk + +TCLINSTALL = 0 # Tk always builds against Tcl source, not an installed Tcl +!if "$(TCLDIR)" == "" +!if [echo TCLDIR = \> nmakehlp.out] \ + || [nmakehlp -L generic\tcl.h >> nmakehlp.out] +!error *** Could not locate Tcl source directory. +!endif +!include nmakehlp.out +!endif # TCLDIR == "" + +_TCLDIR = $(TCLDIR:/=\) +_TCL_H = $(_TCLDIR)\generic\tcl.h +!if !exist("$(_TCL_H)") +!error Could not locate tcl.h. Please set the TCLDIR macro to point to the Tcl *source* directory. +!endif + +_TK_H = ..\generic\tk.h + +# END Case 2(b) - Building Tk + +!else + +# BEGIN Case 2(c) or (d) - Building an extension other than Tk + +# If command line has specified Tcl location through TCLDIR, use it +# else default to the INSTALLDIR setting +!if "$(TCLDIR)" != "" + +_TCLDIR = $(TCLDIR:/=\) +!if exist("$(_TCLDIR)\include\tcl.h") # Case 2(c) with TCLDIR defined +TCLINSTALL = 1 +_TCL_H = $(_TCLDIR)\include\tcl.h +!elseif exist("$(_TCLDIR)\generic\tcl.h") # Case 2(d) with TCLDIR defined +TCLINSTALL = 0 +_TCL_H = $(_TCLDIR)\generic\tcl.h +!endif + +!else # # Case 2(c) for extensions with TCLDIR undefined + +# Need to locate Tcl depending on whether it needs Tcl source or not. +# If we don't, check the INSTALLDIR for an installed Tcl first + +!if exist("$(_INSTALLDIR)\include\tcl.h") && !$(NEED_TCL_SOURCE) + +TCLINSTALL = 1 +TCLDIR = $(_INSTALLDIR)\.. +# NOTE: we will be resetting _INSTALLDIR to _INSTALLDIR/lib for extensions +# later so the \.. accounts for the /lib +_TCLDIR = $(_INSTALLDIR)\.. +_TCL_H = $(_TCLDIR)\include\tcl.h + +!else # exist(...) && !$(NEED_TCL_SOURCE) + +!if [echo _TCLDIR = \> nmakehlp.out] \ + || [nmakehlp -L generic\tcl.h >> nmakehlp.out] +!error *** Could not locate Tcl source directory. +!endif +!include nmakehlp.out +TCLINSTALL = 0 +TCLDIR = $(_TCLDIR) +_TCL_H = $(_TCLDIR)\generic\tcl.h + +!endif # exist(...) && !$(NEED_TCL_SOURCE) + +!endif # TCLDIR + +!ifndef _TCL_H +MSG =^ +Failed to find tcl.h. The TCLDIR macro is set incorrectly or is not set and default path does not contain tcl.h. +!error $(MSG) +!endif + +# Now do the same to locate Tk headers and libs if project requires Tk +!if $(NEED_TK) + +!if "$(TKDIR)" != "" + +_TKDIR = $(TKDIR:/=\) +!if exist("$(_TKDIR)\include\tk.h") +TKINSTALL = 1 +_TK_H = $(_TKDIR)\include\tk.h +!elseif exist("$(_TKDIR)\generic\tk.h") +TKINSTALL = 0 +_TK_H = $(_TKDIR)\generic\tk.h +!endif + +!else # TKDIR not defined + +# Need to locate Tcl depending on whether it needs Tcl source or not. +# If we don't, check the INSTALLDIR for an installed Tcl first + +!if exist("$(_INSTALLDIR)\include\tk.h") && !$(NEED_TK_SOURCE) + +TKINSTALL = 1 +# NOTE: we will be resetting _INSTALLDIR to _INSTALLDIR/lib for extensions +# later so the \.. accounts for the /lib +_TKDIR = $(_INSTALLDIR)\.. +_TK_H = $(_TKDIR)\include\tk.h +TKDIR = $(_TKDIR) + +!else # exist("$(_INSTALLDIR)\include\tk.h") && !$(NEED_TK_SOURCE) + +!if [echo _TKDIR = \> nmakehlp.out] \ + || [nmakehlp -L generic\tk.h >> nmakehlp.out] +!error *** Could not locate Tk source directory. +!endif +!include nmakehlp.out +TKINSTALL = 0 +TKDIR = $(_TKDIR) +_TK_H = $(_TKDIR)\generic\tk.h + +!endif # exist("$(_INSTALLDIR)\include\tk.h") && !$(NEED_TK_SOURCE) + +!endif # TKDIR + +!ifndef _TK_H +MSG =^ +Failed to find tk.h. The TKDIR macro is set incorrectly or is not set and default path does not contain tk.h. +!error $(MSG) +!endif + +!endif # NEED_TK + +!if $(NEED_TCL_SOURCE) && $(TCLINSTALL) +MSG = ^ +*** Warning: This extension requires the source distribution of Tcl.^ +*** Please set the TCLDIR macro to point to the Tcl sources. +!error $(MSG) +!endif + +!if $(NEED_TK_SOURCE) +!if $(TKINSTALL) +MSG = ^ +*** Warning: This extension requires the source distribution of Tk.^ +*** Please set the TKDIR macro to point to the Tk sources. +!error $(MSG) +!endif +!endif + + +# If INSTALLDIR set to Tcl installation root dir then reset to the +# lib dir for installing extensions +!if exist("$(_INSTALLDIR)\include\tcl.h") +_INSTALLDIR=$(_INSTALLDIR)\lib +!endif + +# END Case 2(c) or (d) - Building an extension +!endif # if $(DOING_TCL) + +################################################################ +# 3. Determine compiler version and architecture +# In this section, we figure out the compiler version and the +# architecture for which we are building. This sets the +# following macros: +# VCVERSION - the internal compiler version as 1200, 1400, 1910 etc. +# This is also printed by the compiler in dotted form 19.10 etc. +# VCVER - the "marketing version", for example Visual C++ 6 for internal +# compiler version 1200. This is kept only for legacy reasons as it +# does not make sense for recent Microsoft compilers. Only used for +# output directory names. +# ARCH - set to IX86, ARM64 or AMD64 depending on 32- or 64-bit target +# NATIVE_ARCH - set to IX86, ARM64 or AMD64 for the host machine +# MACHINE - same as $(ARCH) - legacy +# _VC_MANIFEST_EMBED_{DLL,EXE} - commands for embedding a manifest if needed + +cc32 = $(CC) # built-in default. +link32 = link +lib32 = lib +rc32 = $(RC) # built-in default. + +#---------------------------------------------------------------- +# Figure out the compiler architecture and version by writing +# the C macros to a file, preprocessing them with the C +# preprocessor and reading back the created file + +_HASH=^# +_VC_MANIFEST_EMBED_EXE= +_VC_MANIFEST_EMBED_DLL= +VCVER=0 +!if ![echo VCVERSION=_MSC_VER > vercl.x] \ + && ![echo $(_HASH)if defined(_M_IX86) >> vercl.x] \ + && ![echo ARCH=IX86 >> vercl.x] \ + && ![echo $(_HASH)elif defined(_M_AMD64) >> vercl.x] \ + && ![echo ARCH=AMD64 >> vercl.x] \ + && ![echo $(_HASH)elif defined(_M_ARM64) >> vercl.x] \ + && ![echo ARCH=ARM64 >> vercl.x] \ + && ![echo $(_HASH)endif >> vercl.x] \ + && ![$(cc32) -nologo -TC -P vercl.x 2>NUL] +!include vercl.i +!if $(VCVERSION) < 1900 +!if ![echo VCVER= ^\> vercl.vc] \ + && ![set /a $(VCVERSION) / 100 - 6 >> vercl.vc] +!include vercl.vc +!endif +!else +# The simple calculation above does not apply to new Visual Studio releases +# Keep the compiler version in its native form. +VCVER = $(VCVERSION) +!endif +!endif + +!if ![del 2>NUL /q/f vercl.x vercl.i vercl.vc] +!endif + +#---------------------------------------------------------------- +# The MACHINE macro is used by legacy makefiles so set it as well +!ifdef MACHINE +!if "$(MACHINE)" == "x86" +!undef MACHINE +MACHINE = IX86 +!elseif "$(MACHINE)" == "arm64" +!undef MACHINE +MACHINE = ARM64 +!elseif "$(MACHINE)" == "x64" +!undef MACHINE +MACHINE = AMD64 +!endif +!if "$(MACHINE)" != "$(ARCH)" +!error Specified MACHINE macro $(MACHINE) does not match detected target architecture $(ARCH). +!endif +!else +MACHINE=$(ARCH) +!endif + +#--------------------------------------------------------------- +# The PLATFORM_IDENTIFY macro matches the values returned by +# the Tcl platform::identify command +!if "$(MACHINE)" == "AMD64" +PLATFORM_IDENTIFY = win32-x86_64 +!elseif "$(MACHINE)" == "ARM64" +PLATFORM_IDENTIFY = win32-arm +!else +PLATFORM_IDENTIFY = win32-ix86 +!endif + +# The MULTIPLATFORM macro controls whether binary extensions are installed +# in platform-specific directories. Intended to be set/used by extensions. +!ifndef MULTIPLATFORM_INSTALL +MULTIPLATFORM_INSTALL = 0 +!endif + +#------------------------------------------------------------ +# Figure out the *host* architecture by reading the registry + +!if ![reg query HKLM\Hardware\Description\System\CentralProcessor\0 /v Identifier | findstr /i x86] +NATIVE_ARCH=IX86 +!elseif ![reg query HKLM\Hardware\Description\System\CentralProcessor\0 /v Identifier | findstr /i ARM | findstr /i 64-bit] +NATIVE_ARCH=ARM64 +!else +NATIVE_ARCH=AMD64 +!endif + +# Since MSVC8 we must deal with manifest resources. +!if $(VCVERSION) >= 1400 +_VC_MANIFEST_EMBED_EXE=if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;1 +_VC_MANIFEST_EMBED_DLL=if exist $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;2 +!endif + +################################################################ +# 4. Build the nmakehlp program +# This is a helper app we need to overcome nmake's limiting +# environment. We will call out to it to get various bits of +# information about supported compiler options etc. +# +# Tcl itself will always use the nmakehlp.c program which is +# in its own source. It will be kept updated there. +# +# Extensions built against an installed Tcl will use the installed +# copy of Tcl's nmakehlp.c if there is one and their own version +# otherwise. In the latter case, they would also be using their own +# rules.vc. Note that older versions of Tcl do not install nmakehlp.c +# or rules.vc. +# +# Extensions built against Tcl sources will use the one from the Tcl source. +# +# When building an extension using a sufficiently new version of Tcl, +# rules-ext.vc will define NMAKEHLPC appropriately to point to the +# copy of nmakehlp.c to be used. + +!ifndef NMAKEHLPC +# Default to the one in the current directory (the extension's own nmakehlp.c) +NMAKEHLPC = nmakehlp.c + +!if !$(DOING_TCL) +!if $(TCLINSTALL) +!if exist("$(_TCLDIR)\lib\nmake\nmakehlp.c") +NMAKEHLPC = $(_TCLDIR)\lib\nmake\nmakehlp.c +!endif +!else # !$(TCLINSTALL) +!if exist("$(_TCLDIR)\win\nmakehlp.c") +NMAKEHLPC = $(_TCLDIR)\win\nmakehlp.c +!endif +!endif # $(TCLINSTALL) +!endif # !$(DOING_TCL) + +!endif # NMAKEHLPC + +# We always build nmakehlp even if it exists since we do not know +# what source it was built from. +!if "$(MACHINE)" == "IX86" || "$(MACHINE)" == "$(NATIVE_ARCH)" +!if [$(cc32) -nologo "$(NMAKEHLPC)" -link -subsystem:console > nul] +!endif +!else +!if [copy $(NMAKEHLPC:nmakehlp.c=x86_64-w64-mingw32-nmakehlp.exe) nmakehlp.exe >NUL] +!endif +!endif + +################################################################ +# 5. Test for compiler features +# Visual C++ compiler options have changed over the years. Check +# which options are supported by the compiler in use. +# +# The following macros are set: +# OPTIMIZATIONS - the compiler flags to be used for optimized builds +# DEBUGFLAGS - the compiler flags to be used for debug builds +# LINKERFLAGS - Flags passed to the linker +# +# Note that these are the compiler settings *available*, not those +# that will be *used*. The latter depends on the OPTS macro settings +# which we have not yet parsed. +# +# Also note that some of the flags in OPTIMIZATIONS are not really +# related to optimization. They are placed there only for legacy reasons +# as some extensions expect them to be included in that macro. + +# -Op improves float consistency. Note only needed for older compilers +# Newer compilers do not need or support this option. +!if [nmakehlp -c -Op] +FPOPTS = -Op +!endif + +# Strict floating point semantics - present in newer compilers in lieu of -Op +!if [nmakehlp -c -fp:strict] +FPOPTS = $(FPOPTS) -fp:strict +!endif + +!if "$(MACHINE)" == "IX86" +### test for pentium errata +!if [nmakehlp -c -QI0f] +!message *** Compiler has 'Pentium 0x0f fix' +FPOPTS = $(FPOPTS) -QI0f +!else +!message *** Compiler does not have 'Pentium 0x0f fix' +!endif +!endif + +### test for optimizations +# /O2 optimization includes /Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy as per +# documentation. Note we do NOT want /Gs as that inserts a _chkstk +# stack probe at *every* function entry, not just those with more than +# a page of stack allocation resulting in a performance hit. However, +# /O2 documentation is misleading as its stack probes are simply the +# default page size locals allocation probes and not what is implied +# by an explicit /Gs option. + +OPTIMIZATIONS = $(FPOPTS) + +!if [nmakehlp -c -O2] +OPTIMIZING = 1 +OPTIMIZATIONS = $(OPTIMIZATIONS) -O2 +!else +# Legacy, really. All modern compilers support this +!message *** Compiler does not have 'Optimizations' +OPTIMIZING = 0 +!endif + +# Checks for buffer overflows in local arrays +!if [nmakehlp -c -GS] +OPTIMIZATIONS = $(OPTIMIZATIONS) -GS +!endif + +# Link time optimization. Note that this option (potentially) makes +# generated libraries only usable by the specific VC++ version that +# created it. Requires /LTCG linker option +!if [nmakehlp -c -GL] +OPTIMIZATIONS = $(OPTIMIZATIONS) -GL +CC_GL_OPT_ENABLED = 1 +!else +# In newer compilers -GL and -YX are incompatible. +!if [nmakehlp -c -YX] +OPTIMIZATIONS = $(OPTIMIZATIONS) -YX +!endif +!endif # [nmakehlp -c -GL] + +DEBUGFLAGS = $(FPOPTS) + +# Run time error checks. Not available or valid in a release, non-debug build +# RTC is for modern compilers, -GZ is legacy +!if [nmakehlp -c -RTC1] +DEBUGFLAGS = $(DEBUGFLAGS) -RTC1 +!elseif [nmakehlp -c -GZ] +DEBUGFLAGS = $(DEBUGFLAGS) -GZ +!endif + +#---------------------------------------------------------------- +# Linker flags + +# LINKER_TESTFLAGS are for internal use when we call nmakehlp to test +# if the linker supports a specific option. Without these flags link will +# return "LNK1561: entry point must be defined" error compiling from VS-IDE: +# They are not passed through to the actual application / extension +# link rules. +!ifndef LINKER_TESTFLAGS +LINKER_TESTFLAGS = /DLL /NOENTRY /OUT:nmakehlp.out +!endif + +LINKERFLAGS = + +# If compiler has enabled link time optimization, linker must too with -ltcg +!ifdef CC_GL_OPT_ENABLED +!if [nmakehlp -l -ltcg $(LINKER_TESTFLAGS)] +LINKERFLAGS = $(LINKERFLAGS) -ltcg +!endif +!endif + + +################################################################ +# 6. Extract various version numbers from headers +# For Tcl and Tk, version numbers are extracted from tcl.h and tk.h +# respectively. For extensions, versions are extracted from the +# configure.in or configure.ac from the TEA configuration if it +# exists, and unset otherwise. +# Sets the following macros: +# TCL_MAJOR_VERSION +# TCL_MINOR_VERSION +# TCL_RELEASE_SERIAL +# TCL_PATCH_LEVEL +# TCL_PATCH_LETTER +# TCL_VERSION +# TK_MAJOR_VERSION +# TK_MINOR_VERSION +# TK_RELEASE_SERIAL +# TK_PATCH_LEVEL +# TK_PATCH_LETTER +# TK_VERSION +# DOTVERSION - set as (for example) 2.5 +# VERSION - set as (for example 25) +#-------------------------------------------------------------- + +!if [echo REM = This file is generated from rules.vc > versions.vc] +!endif +!if [echo TCL_MAJOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" "define TCL_MAJOR_VERSION" >> versions.vc] +!endif +!if [echo TCL_MINOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" "define TCL_MINOR_VERSION" >> versions.vc] +!endif +!if [echo TCL_RELEASE_SERIAL = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" TCL_RELEASE_SERIAL >> versions.vc] +!endif +!if [echo TCL_PATCH_LEVEL = \>> versions.vc] \ + && [nmakehlp -V "$(_TCL_H)" TCL_PATCH_LEVEL >> versions.vc] +!endif + +!if defined(_TK_H) +!if [echo TK_MAJOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V $(_TK_H) "define TK_MAJOR_VERSION" >> versions.vc] +!endif +!if [echo TK_MINOR_VERSION = \>> versions.vc] \ + && [nmakehlp -V $(_TK_H) TK_MINOR_VERSION >> versions.vc] +!endif +!if [echo TK_RELEASE_SERIAL = \>> versions.vc] \ + && [nmakehlp -V "$(_TK_H)" TK_RELEASE_SERIAL >> versions.vc] +!endif +!if [echo TK_PATCH_LEVEL = \>> versions.vc] \ + && [nmakehlp -V $(_TK_H) TK_PATCH_LEVEL >> versions.vc] +!endif +!endif # _TK_H + +!include versions.vc + +TCL_VERSION = $(TCL_MAJOR_VERSION)$(TCL_MINOR_VERSION) +TCL_DOTVERSION = $(TCL_MAJOR_VERSION).$(TCL_MINOR_VERSION) +!if [nmakehlp -f $(TCL_PATCH_LEVEL) "a"] +TCL_PATCH_LETTER = a +!elseif [nmakehlp -f $(TCL_PATCH_LEVEL) "b"] +TCL_PATCH_LETTER = b +!else +TCL_PATCH_LETTER = . +!endif + +!if defined(_TK_H) + +TK_VERSION = $(TK_MAJOR_VERSION)$(TK_MINOR_VERSION) +TK_DOTVERSION = $(TK_MAJOR_VERSION).$(TK_MINOR_VERSION) +!if [nmakehlp -f $(TK_PATCH_LEVEL) "a"] +TK_PATCH_LETTER = a +!elseif [nmakehlp -f $(TK_PATCH_LEVEL) "b"] +TK_PATCH_LETTER = b +!else +TK_PATCH_LETTER = . +!endif + +!endif + +# Set DOTVERSION and VERSION +!if $(DOING_TCL) + +DOTVERSION = $(TCL_MAJOR_VERSION).$(TCL_MINOR_VERSION) +VERSION = $(TCL_VERSION) + +!elseif $(DOING_TK) + +DOTVERSION = $(TK_DOTVERSION) +VERSION = $(TK_VERSION) + +!else # Doing a non-Tk extension + +# If parent makefile has not defined DOTVERSION, try to get it from TEA +# first from a configure.in file, and then from configure.ac +!ifndef DOTVERSION +!if [echo DOTVERSION = \> versions.vc] \ + || [nmakehlp -V $(ROOT)\configure.in ^[$(PROJECT)^] >> versions.vc] +!if [echo DOTVERSION = \> versions.vc] \ + || [nmakehlp -V $(ROOT)\configure.ac ^[$(PROJECT)^] >> versions.vc] +!error *** Could not figure out extension version. Please define DOTVERSION in parent makefile before including rules.vc. +!endif +!endif +!include versions.vc +!endif # DOTVERSION +VERSION = $(DOTVERSION:.=) + +!endif # $(DOING_TCL) ... etc. + +# Windows RC files have 3 version components. Ensure this irrespective +# of how many components the package has specified. Basically, ensure +# minimum 4 components by appending 4 0's and then pick out the first 4. +# Also take care of the fact that DOTVERSION may have "a" or "b" instead +# of "." separating the version components. +DOTSEPARATED=$(DOTVERSION:a=.) +DOTSEPARATED=$(DOTSEPARATED:b=.) +!if [echo RCCOMMAVERSION = \> versions.vc] \ + || [for /f "tokens=1,2,3,4,5* delims=." %a in ("$(DOTSEPARATED).0.0.0.0") do echo %a,%b,%c,%d >> versions.vc] +!error *** Could not generate RCCOMMAVERSION *** +!endif +!include versions.vc + +######################################################################## +# 7. Parse the OPTS macro to work out the requested build configuration. +# Based on this, we will construct the actual switches to be passed to the +# compiler and linker using the macros defined in the previous section. +# The following macros are defined by this section based on OPTS +# STATIC_BUILD - 0 -> Tcl is to be built as a shared library +# 1 -> build as a static library and shell +# TCL_THREADS - legacy but always 1 on Windows since winsock requires it. +# DEBUG - 1 -> debug build, 0 -> release builds +# SYMBOLS - 1 -> generate PDB's, 0 -> no PDB's +# PROFILE - 1 -> generate profiling info, 0 -> no profiling +# PGO - 1 -> profile based optimization, 0 -> no +# MSVCRT - 1 -> link to dynamic C runtime even when building static Tcl build +# 0 -> link to static C runtime for static Tcl build. +# Does not impact shared Tcl builds (STATIC_BUILD == 0) +# Default: 1 for Tcl 9.0 and up, 0 otherwise. +# TCL_USE_STATIC_PACKAGES - 1 -> statically link the registry and dde extensions +# in the Tcl and Wish shell. 0 -> keep them as shared libraries. Does +# not impact shared Tcl builds. Implied by STATIC_BUILD since Tcl 9.0. +# USE_THREAD_ALLOC - 1 -> Use a shared global free pool for allocation. +# 0 -> Use the non-thread allocator. +# UNCHECKED - 1 -> when doing a debug build with symbols, use the release +# C runtime, 0 -> use the debug C runtime. +# USE_STUBS - 1 -> compile to use stubs interfaces, 0 -> direct linking +# CONFIG_CHECK - 1 -> check current build configuration against Tcl +# configuration (ignored for Tcl itself) +# _USE_64BIT_TIME_T - forces a build using 64-bit time_t for 32-bit build +# (CRT library should support this, not needed for Tcl 9.x) +# Further, LINKERFLAGS are modified based on above. + +# Default values for all the above +STATIC_BUILD = 0 +TCL_THREADS = 1 +DEBUG = 0 +SYMBOLS = 0 +PROFILE = 0 +PGO = 0 +MSVCRT = 1 +TCL_USE_STATIC_PACKAGES = 0 +USE_THREAD_ALLOC = 1 +UNCHECKED = 0 +CONFIG_CHECK = 1 +!if $(DOING_TCL) +USE_STUBS = 0 +!else +USE_STUBS = 1 +!endif + +# If OPTS is not empty AND does not contain "none" which turns off all OPTS +# set the above macros based on OPTS content +!if "$(OPTS)" != "" && ![nmakehlp -f "$(OPTS)" "none"] + +# OPTS are specified, parse them + +!if [nmakehlp -f $(OPTS) "static"] +!message *** Doing static +STATIC_BUILD = 1 +!endif + +!if [nmakehlp -f $(OPTS) "nostubs"] +!message *** Not using stubs +USE_STUBS = 0 +!endif + +!if [nmakehlp -f $(OPTS) "nomsvcrt"] +!message *** Doing nomsvcrt +MSVCRT = 0 +!else +!if [nmakehlp -f $(OPTS) "msvcrt"] +!message *** Doing msvcrt +!else +!if $(TCL_MAJOR_VERSION) == 8 && $(TCL_MINOR_VERSION) < 7 && $(STATIC_BUILD) +MSVCRT = 0 +!endif +!endif +!endif # [nmakehlp -f $(OPTS) "nomsvcrt"] + +!if [nmakehlp -f $(OPTS) "staticpkg"] && $(STATIC_BUILD) +!message *** Doing staticpkg +TCL_USE_STATIC_PACKAGES = 1 +!endif + +!if [nmakehlp -f $(OPTS) "nothreads"] +!message *** Compile explicitly for non-threaded tcl +TCL_THREADS = 0 +USE_THREAD_ALLOC= 0 +!endif + +!if [nmakehlp -f $(OPTS) "tcl8"] +!message *** Build for Tcl8 +TCL_BUILD_FOR = 8 +!endif + +!if $(TCL_MAJOR_VERSION) == 8 +!if [nmakehlp -f $(OPTS) "time64bit"] +!message *** Force 64-bit time_t +_USE_64BIT_TIME_T = 1 +!endif +!endif + +# Yes, it's weird that the "symbols" option controls DEBUG and +# the "pdbs" option controls SYMBOLS. That's historical. +!if [nmakehlp -f $(OPTS) "symbols"] +!message *** Doing symbols +DEBUG = 1 +!else +DEBUG = 0 +!endif + +!if [nmakehlp -f $(OPTS) "pdbs"] +!message *** Doing pdbs +SYMBOLS = 1 +!else +SYMBOLS = 0 +!endif + +!if [nmakehlp -f $(OPTS) "profile"] +!message *** Doing profile +PROFILE = 1 +!else +PROFILE = 0 +!endif + +!if [nmakehlp -f $(OPTS) "pgi"] +!message *** Doing profile guided optimization instrumentation +PGO = 1 +!elseif [nmakehlp -f $(OPTS) "pgo"] +!message *** Doing profile guided optimization +PGO = 2 +!else +PGO = 0 +!endif + +!if [nmakehlp -f $(OPTS) "loimpact"] +!message *** Warning: ignoring option "loimpact" - deprecated on modern Windows. +!endif + +# TBD - should get rid of this option +!if [nmakehlp -f $(OPTS) "thrdalloc"] +!message *** Doing thrdalloc +USE_THREAD_ALLOC = 1 +!endif + +!if [nmakehlp -f $(OPTS) "tclalloc"] +USE_THREAD_ALLOC = 0 +!endif + +!if [nmakehlp -f $(OPTS) "unchecked"] +!message *** Doing unchecked +UNCHECKED = 1 +!else +UNCHECKED = 0 +!endif + +!if [nmakehlp -f $(OPTS) "noconfigcheck"] +CONFIG_CHECK = 1 +!else +CONFIG_CHECK = 0 +!endif + +!endif # "$(OPTS)" != "" && ... parsing of OPTS + +# Set linker flags based on above + +!if $(PGO) > 1 +!if [nmakehlp -l -ltcg:pgoptimize $(LINKER_TESTFLAGS)] +LINKERFLAGS = $(LINKERFLAGS:-ltcg=) -ltcg:pgoptimize +!else +MSG=^ +This compiler does not support profile guided optimization. +!error $(MSG) +!endif +!elseif $(PGO) > 0 +!if [nmakehlp -l -ltcg:pginstrument $(LINKER_TESTFLAGS)] +LINKERFLAGS = $(LINKERFLAGS:-ltcg=) -ltcg:pginstrument +!else +MSG=^ +This compiler does not support profile guided optimization. +!error $(MSG) +!endif +!endif + +################################################################ +# 8. Parse the STATS macro to configure code instrumentation +# The following macros are set by this section: +# TCL_MEM_DEBUG - 1 -> enables memory allocation instrumentation +# 0 -> disables +# TCL_COMPILE_DEBUG - 1 -> enables byte compiler logging +# 0 -> disables + +# Default both are off +TCL_MEM_DEBUG = 0 +TCL_COMPILE_DEBUG = 0 + +!if "$(STATS)" != "" && ![nmakehlp -f "$(STATS)" "none"] + +!if [nmakehlp -f $(STATS) "memdbg"] +!message *** Doing memdbg +TCL_MEM_DEBUG = 1 +!else +TCL_MEM_DEBUG = 0 +!endif + +!if [nmakehlp -f $(STATS) "compdbg"] +!message *** Doing compdbg +TCL_COMPILE_DEBUG = 1 +!else +TCL_COMPILE_DEBUG = 0 +!endif + +!endif + +#################################################################### +# 9. Parse the CHECKS macro to configure additional compiler checks +# The following macros are set by this section: +# WARNINGS - compiler switches that control the warnings level +# TCL_NO_DEPRECATED - 1 -> disable support for deprecated functions +# 0 -> enable deprecated functions + +# Defaults - Permit deprecated functions and warning level 3 +TCL_NO_DEPRECATED = 0 +WARNINGS = -W3 + +!if "$(CHECKS)" != "" && ![nmakehlp -f "$(CHECKS)" "none"] + +!if [nmakehlp -f $(CHECKS) "nodep"] +!message *** Doing nodep check +TCL_NO_DEPRECATED = 1 +!endif + +!if [nmakehlp -f $(CHECKS) "fullwarn"] +!message *** Doing full warnings check +WARNINGS = -W4 +!if [nmakehlp -l -warn:3 $(LINKER_TESTFLAGS)] +LINKERFLAGS = $(LINKERFLAGS) -warn:3 +!endif +!endif + +!if [nmakehlp -f $(CHECKS) "64bit"] && [nmakehlp -c -Wp64] +!message *** Doing 64bit portability warnings +WARNINGS = $(WARNINGS) -Wp64 +!endif + +!endif + + +################################################################ +# 10. Construct output directory and file paths +# Figure-out how to name our intermediate and output directories. +# In order to avoid inadvertent mixing of object files built using +# different compilers, build configurations etc., +# +# Naming convention (suffixes): +# t = full thread support. (Not used for Tcl >= 9.0) +# s = static library (as opposed to an import library) +# g = linked to the debug enabled C run-time. +# x = special static build when it links to the dynamic C run-time. +# +# The following macros are set in this section: +# SUFX - the suffix to use for binaries based on above naming convention +# BUILDDIRTOP - the toplevel default output directory +# is of the form {Release,Debug}[_AMD64][_COMPILERVERSION] +# TMP_DIR - directory where object files are created +# OUT_DIR - directory where output executables are created +# Both TMP_DIR and OUT_DIR are defaulted only if not defined by the +# parent makefile (or command line). The default values are +# based on BUILDDIRTOP. +# STUBPREFIX - name of the stubs library for this project +# PRJIMPLIB - output path of the generated project import library +# PRJLIBNAME - name of generated project library +# PRJLIB - output path of generated project library +# PRJSTUBLIBNAME - name of the generated project stubs library +# PRJSTUBLIB - output path of the generated project stubs library +# RESFILE - output resource file (only if not static build) + +SUFX = tsgx + +!if $(DEBUG) +BUILDDIRTOP = Debug +!else +BUILDDIRTOP = Release +!endif + +!if "$(MACHINE)" != "IX86" +BUILDDIRTOP =$(BUILDDIRTOP)_$(MACHINE) +!endif +!if $(VCVER) > 6 +BUILDDIRTOP =$(BUILDDIRTOP)_VC$(VCVER) +!endif + +!if !$(DEBUG) || $(TCL_VERSION) > 86 || $(DEBUG) && $(UNCHECKED) +SUFX = $(SUFX:g=) +!endif + +TMP_DIRFULL = .\$(BUILDDIRTOP)\$(PROJECT)_ThreadedDynamicStaticX + +!if !$(STATIC_BUILD) +TMP_DIRFULL = $(TMP_DIRFULL:Static=) +SUFX = $(SUFX:s=) +EXT = dll +TMP_DIRFULL = $(TMP_DIRFULL:X=) +SUFX = $(SUFX:x=) +!else +TMP_DIRFULL = $(TMP_DIRFULL:Dynamic=) +EXT = lib +!if $(MSVCRT) && $(TCL_VERSION) > 86 || !$(MSVCRT) && $(TCL_VERSION) < 87 +TMP_DIRFULL = $(TMP_DIRFULL:X=) +SUFX = $(SUFX:x=) +!endif +!endif + +!if !$(TCL_THREADS) || $(TCL_VERSION) > 86 +TMP_DIRFULL = $(TMP_DIRFULL:Threaded=) +SUFX = $(SUFX:t=) +!endif + +!ifndef TMP_DIR +TMP_DIR = $(TMP_DIRFULL) +!ifndef OUT_DIR +OUT_DIR = .\$(BUILDDIRTOP) +!endif +!else +!ifndef OUT_DIR +OUT_DIR = $(TMP_DIR) +!endif +!endif + +# Relative paths -> absolute +!if [echo OUT_DIR = \> nmakehlp.out] \ + || [nmakehlp -Q "$(OUT_DIR)" >> nmakehlp.out] +!error *** Could not fully qualify path OUT_DIR=$(OUT_DIR) +!endif +!if [echo TMP_DIR = \>> nmakehlp.out] \ + || [nmakehlp -Q "$(TMP_DIR)" >> nmakehlp.out] +!error *** Could not fully qualify path TMP_DIR=$(TMP_DIR) +!endif +!include nmakehlp.out + +# The name of the stubs library for the project being built +STUBPREFIX = $(PROJECT)stub + +# +# Set up paths to various Tcl executables and libraries needed by extensions +# + +# TIP 430. Unused for 8.6 but no harm defining it to allow a common rules.vc +TCL_ZIP_FILE = libtcl$(TCL_MAJOR_VERSION).$(TCL_MINOR_VERSION)$(TCL_PATCH_LETTER)$(TCL_RELEASE_SERIAL).zip +TK_ZIP_FILE = libtk$(TK_MAJOR_VERSION).$(TK_MINOR_VERSION)$(TK_PATCH_LETTER)$(TK_RELEASE_SERIAL).zip + +!if $(DOING_TCL) +TCLSHNAME = $(PROJECT)sh$(VERSION)$(SUFX).exe +TCLSH = $(OUT_DIR)\$(TCLSHNAME) +TCLIMPLIB = $(OUT_DIR)\$(PROJECT)$(VERSION)$(SUFX).lib +TCLLIBNAME = $(PROJECT)$(VERSION)$(SUFX).$(EXT) +TCLLIB = $(OUT_DIR)\$(TCLLIBNAME) +TCLSCRIPTZIP = $(OUT_DIR)\$(TCL_ZIP_FILE) + +!if $(TCL_MAJOR_VERSION) == 8 +TCLSTUBLIBNAME = $(STUBPREFIX)$(VERSION).lib +!else +TCLSTUBLIBNAME = $(STUBPREFIX).lib +!endif +TCLSTUBLIB = $(OUT_DIR)\$(TCLSTUBLIBNAME) +TCL_INCLUDES = -I"$(WIN_DIR)" -I"$(GENERICDIR)" + +!else # !$(DOING_TCL) + +!if $(TCLINSTALL) # Building against an installed Tcl + +# When building extensions, we need to locate tclsh. Depending on version +# of Tcl we are building against, this may or may not have a "t" suffix. +# Try various possibilities in turn. +TCLSH = $(_TCLDIR)\bin\tclsh$(TCL_VERSION)$(SUFX:t=).exe +!if !exist("$(TCLSH)") +TCLSH = $(_TCLDIR)\bin\tclsh$(TCL_VERSION)t$(SUFX:t=).exe +!endif + +!if $(TCL_MAJOR_VERSION) == 8 +TCLSTUBLIB = $(_TCLDIR)\lib\tclstub$(TCL_VERSION).lib +!else +TCLSTUBLIB = $(_TCLDIR)\lib\tclstub.lib +!endif +TCLIMPLIB = $(_TCLDIR)\lib\tcl$(TCL_VERSION)$(SUFX:t=).lib +# When building extensions, may be linking against Tcl that does not add +# "t" suffix (e.g. 8.6). If lib not found check for that possibility. +!if !exist("$(TCLIMPLIB)") +TCLIMPLIB = $(_TCLDIR)\lib\tcl$(TCL_VERSION)t$(SUFX:t=).lib +!endif +TCL_LIBRARY = $(_TCLDIR)\lib +TCLREGLIB = $(_TCLDIR)\lib\tclreg13$(SUFX:t=).lib +TCLDDELIB = $(_TCLDIR)\lib\tcldde14$(SUFX:t=).lib +TCLSCRIPTZIP = $(_TCLDIR)\lib\$(TCL_ZIP_FILE) +TCLTOOLSDIR = \must\have\tcl\sources\to\build\this\target +TCL_INCLUDES = -I"$(_TCLDIR)\include" + +!else # Building against Tcl sources + +TCLSH = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclsh$(TCL_VERSION)$(SUFX:t=).exe +!if !exist($(TCLSH)) +TCLSH = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclsh$(TCL_VERSION)t$(SUFX:t=).exe +!endif +!if $(TCL_MAJOR_VERSION) == 8 +TCLSTUBLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclstub$(TCL_VERSION).lib +!else +TCLSTUBLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclstub.lib +!endif +TCLIMPLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tcl$(TCL_VERSION)$(SUFX:t=).lib +# When building extensions, may be linking against Tcl that does not add +# "t" suffix (e.g. 8.6). If lib not found check for that possibility. +!if !exist("$(TCLIMPLIB)") +TCLIMPLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tcl$(TCL_VERSION)t$(SUFX:t=).lib +!endif +TCL_LIBRARY = $(_TCLDIR)\library +TCLREGLIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tclreg13$(SUFX:t=).lib +TCLDDELIB = $(_TCLDIR)\win\$(BUILDDIRTOP)\tcldde14$(SUFX:t=).lib +TCLSCRIPTZIP = $(_TCLDIR)\win\$(BUILDDIRTOP)\$(TCL_ZIP_FILE) +TCLTOOLSDIR = $(_TCLDIR)\tools +TCL_INCLUDES = -I"$(_TCLDIR)\generic" -I"$(_TCLDIR)\win" + +!endif # TCLINSTALL + +!if !$(STATIC_BUILD) && "$(TCL_BUILD_FOR)" == "8" +tcllibs = "$(TCLSTUBLIB)" +!else +tcllibs = "$(TCLSTUBLIB)" "$(TCLIMPLIB)" +!endif + +!endif # $(DOING_TCL) + +# We need a tclsh that will run on the host machine as part of the build. +# IX86 runs on all architectures. +!ifndef TCLSH_NATIVE +!if "$(MACHINE)" == "IX86" || "$(MACHINE)" == "$(NATIVE_ARCH)" +TCLSH_NATIVE = $(TCLSH) +!else +!error You must explicitly set TCLSH_NATIVE for cross-compilation +!endif +!endif + +# Do the same for Tk and Tk extensions that require the Tk libraries +!if $(DOING_TK) || $(NEED_TK) +WISHNAMEPREFIX = wish +WISHNAME = $(WISHNAMEPREFIX)$(TK_VERSION)$(SUFX).exe +TKLIBNAME8 = tk$(TK_VERSION)$(SUFX).$(EXT) +TKLIBNAME9 = tcl9tk$(TK_VERSION)$(SUFX).$(EXT) +!if $(TCL_MAJOR_VERSION) == 8 || "$(TCL_BUILD_FOR)" == "8" +TKLIBNAME = tk$(TK_VERSION)$(SUFX).$(EXT) +TKIMPLIBNAME = tk$(TK_VERSION)$(SUFX).lib +!else +TKLIBNAME = tcl9tk$(TK_VERSION)$(SUFX).$(EXT) +TKIMPLIBNAME = tcl9tk$(TK_VERSION)$(SUFX).lib +!endif +!if $(TK_MAJOR_VERSION) == 8 +TKSTUBLIBNAME = tkstub$(TK_VERSION).lib +!else +TKSTUBLIBNAME = tkstub.lib +!endif + +!if $(DOING_TK) +WISH = $(OUT_DIR)\$(WISHNAME) +TKSTUBLIB = $(OUT_DIR)\$(TKSTUBLIBNAME) +TKIMPLIB = $(OUT_DIR)\$(TKIMPLIBNAME) +TKLIB = $(OUT_DIR)\$(TKLIBNAME) +TK_INCLUDES = -I"$(WIN_DIR)" -I"$(GENERICDIR)" +TKSCRIPTZIP = $(OUT_DIR)\$(TK_ZIP_FILE) + +!else # effectively NEED_TK + +!if $(TKINSTALL) # Building against installed Tk +WISH = $(_TKDIR)\bin\$(WISHNAME) +TKSTUBLIB = $(_TKDIR)\lib\$(TKSTUBLIBNAME) +TKIMPLIB = $(_TKDIR)\lib\$(TKIMPLIBNAME) +# When building extensions, may be linking against Tk that does not add +# "t" suffix (e.g. 8.6). If lib not found check for that possibility. +!if !exist("$(TKIMPLIB)") +TKIMPLIBNAME = tk$(TK_VERSION)$(SUFX:t=).lib +TKIMPLIB = $(_TKDIR)\lib\$(TKIMPLIBNAME) +!endif +TK_INCLUDES = -I"$(_TKDIR)\include" +TKSCRIPTZIP = $(_TKDIR)\lib\$(TK_ZIP_FILE) + +!else # Building against Tk sources + +WISH = $(_TKDIR)\win\$(BUILDDIRTOP)\$(WISHNAME) +TKSTUBLIB = $(_TKDIR)\win\$(BUILDDIRTOP)\$(TKSTUBLIBNAME) +TKIMPLIB = $(_TKDIR)\win\$(BUILDDIRTOP)\$(TKIMPLIBNAME) +# When building extensions, may be linking against Tk that does not add +# "t" suffix (e.g. 8.6). If lib not found check for that possibility. +!if !exist("$(TKIMPLIB)") +TKIMPLIBNAME = tk$(TK_VERSION)$(SUFX:t=).lib +TKIMPLIB = $(_TKDIR)\win\$(BUILDDIRTOP)\$(TKIMPLIBNAME) +!endif +TK_INCLUDES = -I"$(_TKDIR)\generic" -I"$(_TKDIR)\win" -I"$(_TKDIR)\xlib" +TKSCRIPTZIP = $(_TKDIR)\win\$(BUILDDIRTOP)\$(TK_ZIP_FILE) + +!endif # TKINSTALL + +tklibs = "$(TKSTUBLIB)" "$(TKIMPLIB)" + +!endif # $(DOING_TK) +!endif # $(DOING_TK) || $(NEED_TK) + +# Various output paths +PRJIMPLIB = $(OUT_DIR)\$(PROJECT)$(VERSION)$(SUFX).lib +# Even when building against Tcl 9, PRJLIBNAME8 must have "t" +PRJLIBNAME8 = $(PROJECT)$(VERSION)t$(SUFX:t=).$(EXT) +# Even when building against Tcl 8, PRJLIBNAME9 must not have "t" +PRJLIBNAME9 = tcl9$(PROJECT)$(VERSION)$(SUFX:t=).$(EXT) +!if $(TCL_MAJOR_VERSION) == 8 || "$(TCL_BUILD_FOR)" == "8" +PRJLIBNAME = $(PRJLIBNAME8) +!else +PRJLIBNAME = $(PRJLIBNAME9) +!endif +PRJLIB = $(OUT_DIR)\$(PRJLIBNAME) + +!if $(TCL_MAJOR_VERSION) == 8 +PRJSTUBLIBNAME = $(STUBPREFIX)$(VERSION).lib +!else +PRJSTUBLIBNAME = $(STUBPREFIX).lib +!endif +PRJSTUBLIB = $(OUT_DIR)\$(PRJSTUBLIBNAME) + +# If extension parent makefile has not defined a resource definition file, +# we will generate one from standard template. +!if !$(DOING_TCL) && !$(DOING_TK) && !$(STATIC_BUILD) +!ifdef RCFILE +RESFILE = $(TMP_DIR)\$(RCFILE:.rc=.res) +!else +RESFILE = $(TMP_DIR)\$(PROJECT).res +!endif +!endif + +################################################################### +# 11. Construct the paths for the installation directories +# The following macros get defined in this section: +# LIB_INSTALL_DIR - where libraries should be installed +# BIN_INSTALL_DIR - where the executables should be installed +# DOC_INSTALL_DIR - where documentation should be installed +# SCRIPT_INSTALL_DIR - where scripts should be installed +# INCLUDE_INSTALL_DIR - where C include files should be installed +# DEMO_INSTALL_DIR - where demos should be installed +# PRJ_INSTALL_DIR - where package will be installed (not set for Tcl and Tk) + +!if $(DOING_TCL) || $(DOING_TK) +LIB_INSTALL_DIR = $(_INSTALLDIR)\lib +BIN_INSTALL_DIR = $(_INSTALLDIR)\bin +DOC_INSTALL_DIR = $(_INSTALLDIR)\doc +!if $(DOING_TCL) +SCRIPT_INSTALL_DIR = $(_INSTALLDIR)\lib\$(PROJECT)$(TCL_MAJOR_VERSION).$(TCL_MINOR_VERSION) +MODULE_INSTALL_DIR = $(_INSTALLDIR)\lib\tcl$(TCL_MAJOR_VERSION) +!else # DOING_TK +SCRIPT_INSTALL_DIR = $(_INSTALLDIR)\lib\$(PROJECT)$(TK_MAJOR_VERSION).$(TK_MINOR_VERSION) +!endif +DEMO_INSTALL_DIR = $(SCRIPT_INSTALL_DIR)\demos +INCLUDE_INSTALL_DIR = $(_INSTALLDIR)\include + +!else # extension other than Tk + +PRJ_INSTALL_DIR = $(_INSTALLDIR)\$(PROJECT)$(DOTVERSION) +!if $(MULTIPLATFORM_INSTALL) +LIB_INSTALL_DIR = $(PRJ_INSTALL_DIR)\$(PLATFORM_IDENTIFY) +BIN_INSTALL_DIR = $(PRJ_INSTALL_DIR)\$(PLATFORM_IDENTIFY) +!else +LIB_INSTALL_DIR = $(PRJ_INSTALL_DIR) +BIN_INSTALL_DIR = $(PRJ_INSTALL_DIR) +!endif +DOC_INSTALL_DIR = $(PRJ_INSTALL_DIR) +SCRIPT_INSTALL_DIR = $(PRJ_INSTALL_DIR) +DEMO_INSTALL_DIR = $(PRJ_INSTALL_DIR)\demos +INCLUDE_INSTALL_DIR = $(_INSTALLDIR)\..\include + +!endif + +################################################################### +# 12. Set up actual options to be passed to the compiler and linker +# Now we have all the information we need, set up the actual flags and +# options that we will pass to the compiler and linker. The main +# makefile should use these in combination with whatever other flags +# and switches are specific to it. +# The following macros are defined, names are for historical compatibility: +# OPTDEFINES - /Dxxx C macro flags based on user-specified OPTS +# COMPILERFLAGS - /Dxxx C macro flags independent of any configuration options +# crt - Compiler switch that selects the appropriate C runtime +# cdebug - Compiler switches related to debug AND optimizations +# cwarn - Compiler switches that set warning levels +# cflags - complete compiler switches (subsumes cdebug and cwarn) +# ldebug - Linker switches controlling debug information and optimization +# lflags - complete linker switches (subsumes ldebug) except subsystem type +# dlllflags - complete linker switches to build DLLs (subsumes lflags) +# conlflags - complete linker switches for console program (subsumes lflags) +# guilflags - complete linker switches for GUI program (subsumes lflags) +# baselibs - minimum Windows libraries required. Parent makefile can +# define PRJ_LIBS before including rules.rc if additional libs are needed + +OPTDEFINES = /DSTDC_HEADERS /DUSE_NMAKE=1 +!if $(VCVERSION) > 1600 +OPTDEFINES = $(OPTDEFINES) /DHAVE_STDINT_H=1 +!else +OPTDEFINES = $(OPTDEFINES) /DMP_NO_STDINT=1 +!endif +!if $(VCVERSION) >= 1800 +OPTDEFINES = $(OPTDEFINES) /DHAVE_INTTYPES_H=1 /DHAVE_STDBOOL_H=1 +!endif + +!if $(TCL_MEM_DEBUG) +OPTDEFINES = $(OPTDEFINES) /DTCL_MEM_DEBUG +!endif +!if $(TCL_COMPILE_DEBUG) +OPTDEFINES = $(OPTDEFINES) /DTCL_COMPILE_DEBUG /DTCL_COMPILE_STATS +!endif +!if $(TCL_THREADS) && $(TCL_VERSION) < 87 +OPTDEFINES = $(OPTDEFINES) /DTCL_THREADS=1 +!if $(USE_THREAD_ALLOC) && $(TCL_VERSION) < 87 +OPTDEFINES = $(OPTDEFINES) /DUSE_THREAD_ALLOC=1 +!endif +!endif +!if $(STATIC_BUILD) +OPTDEFINES = $(OPTDEFINES) /DSTATIC_BUILD +!elseif $(TCL_VERSION) > 86 +OPTDEFINES = $(OPTDEFINES) /DTCL_WITH_EXTERNAL_TOMMATH +!if "$(MACHINE)" == "AMD64" || "$(MACHINE)" == "ARM64" +OPTDEFINES = $(OPTDEFINES) /DMP_64BIT +!endif +!endif +!if $(TCL_NO_DEPRECATED) +OPTDEFINES = $(OPTDEFINES) /DTCL_NO_DEPRECATED +!endif + +!if $(USE_STUBS) +# Note we do not define USE_TCL_STUBS even when building tk since some +# test targets in tk do not use stubs +!if !$(DOING_TCL) +USE_STUBS_DEFS = /DUSE_TCL_STUBS /DUSE_TCLOO_STUBS +!if $(NEED_TK) +USE_STUBS_DEFS = $(USE_STUBS_DEFS) /DUSE_TK_STUBS +!endif +!endif +!endif # USE_STUBS + +!if !$(DEBUG) +OPTDEFINES = $(OPTDEFINES) /DNDEBUG +!if $(OPTIMIZING) +OPTDEFINES = $(OPTDEFINES) /DTCL_CFG_OPTIMIZED +!endif +!endif +!if $(PROFILE) +OPTDEFINES = $(OPTDEFINES) /DTCL_CFG_PROFILED +!endif +!if "$(MACHINE)" == "AMD64" || "$(MACHINE)" == "ARM64" +OPTDEFINES = $(OPTDEFINES) /DTCL_CFG_DO64BIT +!endif +!if $(VCVERSION) < 1300 +OPTDEFINES = $(OPTDEFINES) /DNO_STRTOI64=1 +!endif + +!if $(TCL_MAJOR_VERSION) == 8 +!if "$(_USE_64BIT_TIME_T)" == "1" +OPTDEFINES = $(OPTDEFINES) /D_USE_64BIT_TIME_T=1 +!endif +!endif +!if "$(TCL_BUILD_FOR)" == "8" +OPTDEFINES = $(OPTDEFINES) /DTCL_MAJOR_VERSION=8 /DTK_MAJOR_VERSION=8 +!endif + +# Like the TEA system only set this non empty for non-Tk extensions +# Note: some extensions use PACKAGE_NAME and others use PACKAGE_TCLNAME +# so we pass both +!if !$(DOING_TCL) && !$(DOING_TK) +PKGNAMEFLAGS = /DPACKAGE_NAME="\"$(PRJ_PACKAGE_TCLNAME)\"" \ + /DPACKAGE_TCLNAME="\"$(PRJ_PACKAGE_TCLNAME)\"" \ + /DPACKAGE_VERSION="\"$(DOTVERSION)\"" \ + /DMODULE_SCOPE=extern +!endif + +# crt picks the C run time based on selected OPTS +!if $(MSVCRT) +!if $(DEBUG) && !$(UNCHECKED) +crt = -MDd +!else +crt = -MD +!endif +!else +!if $(DEBUG) && !$(UNCHECKED) +crt = -MTd +!else +crt = -MT +!endif +!endif + +# cdebug includes compiler options for debugging as well as optimization. +!if $(DEBUG) + +# In debugging mode, optimizations need to be disabled +cdebug = -Zi -Od $(DEBUGFLAGS) + +!else + +cdebug = $(OPTIMIZATIONS) +!if $(SYMBOLS) +cdebug = $(cdebug) -Zi +!endif + +!endif # $(DEBUG) + +# cwarn includes default warning levels, also C4090 (buggy) and C4146 is useless. +cwarn = $(WARNINGS) -wd4090 -wd4146 + +!if "$(MACHINE)" == "AMD64" || "$(MACHINE)" == "ARM64" +# Disable pointer<->int warnings related to cast between different sizes +# There are a gadzillion of these due to use of ClientData and +# clutter up compiler +# output increasing chance of a real warning getting lost. So disable them. +# Eventually some day, Tcl will be 64-bit clean. +cwarn = $(cwarn) -wd4311 -wd4312 +!endif + +### Common compiler options that are architecture specific +!if "$(MACHINE)" == "ARM" +carch = /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE +!else +carch = +!endif + +# cpuid is only available on intel machines +!if "$(MACHINE)" == "IX86" || "$(MACHINE)" == "AMD64" +carch = $(carch) /DHAVE_CPUID=1 +!endif + +!if $(DEBUG) +# Turn warnings into errors +cwarn = $(cwarn) -WX +!endif + +INCLUDES = $(TCL_INCLUDES) $(TK_INCLUDES) $(PRJ_INCLUDES) +!if !$(DOING_TCL) && !$(DOING_TK) +INCLUDES = $(INCLUDES) -I"$(GENERICDIR)" -I"$(WIN_DIR)" -I"$(COMPATDIR)" +!endif + +# These flags are defined roughly in the order of the pre-reform +# rules.vc/makefile.vc to help visually compare that the pre- and +# post-reform build logs + +# cflags contains generic flags used for building practically all object files +cflags = -nologo -c $(COMPILERFLAGS) $(carch) $(cwarn) -Fp$(TMP_DIR)^\ $(cdebug) + +!if $(TCL_MAJOR_VERSION) == 8 && $(TCL_MINOR_VERSION) < 7 +cflags = $(cflags) -DTcl_Size=int +!endif + +# appcflags contains $(cflags) and flags for building the application +# object files (e.g. tclsh, or wish) pkgcflags contains $(cflags) plus +# flags used for building shared object files The two differ in the +# BUILD_$(PROJECT) macro which should be defined only for the shared +# library *implementation* and not for its caller interface + +appcflags_nostubs = $(cflags) $(crt) $(INCLUDES) $(TCL_DEFINES) $(PRJ_DEFINES) $(OPTDEFINES) +appcflags = $(appcflags_nostubs) $(USE_STUBS_DEFS) +pkgcflags = $(appcflags) $(PKGNAMEFLAGS) /DBUILD_$(PROJECT) +pkgcflags_nostubs = $(appcflags_nostubs) $(PKGNAMEFLAGS) /DBUILD_$(PROJECT) + +# stubscflags contains $(cflags) plus flags used for building a stubs +# library for the package. Note: /DSTATIC_BUILD is defined in +# $(OPTDEFINES) only if the OPTS configuration indicates a static +# library. However the stubs library is ALWAYS static hence included +# here irrespective of the OPTS setting. +# +# TBD - tclvfs has a comment that stubs libs should not be compiled with -GL +# without stating why. Tcl itself compiled stubs libs with this flag. +# so we do not remove it from cflags. -GL may prevent extensions +# compiled with one VC version to fail to link against stubs library +# compiled with another VC version. Check for this and fix accordingly. +stubscflags = $(cflags) $(PKGNAMEFLAGS) $(PRJ_DEFINES) $(OPTDEFINES) /Zl /GL- /DSTATIC_BUILD $(INCLUDES) $(USE_STUBS_DEFS) + +# Link flags + +!if $(DEBUG) +ldebug = -debug -debugtype:cv +!else +ldebug = -release -opt:ref -opt:icf,3 +!if $(SYMBOLS) +ldebug = $(ldebug) -debug -debugtype:cv +!endif +!endif + +# Note: Profiling is currently only possible with the Visual Studio Enterprise +!if $(PROFILE) +ldebug= $(ldebug) -profile +!endif + +### Declarations common to all linker versions +lflags = -nologo -machine:$(MACHINE) $(LINKERFLAGS) $(ldebug) + +!if $(MSVCRT) && !($(DEBUG) && !$(UNCHECKED)) && $(VCVERSION) >= 1900 +lflags = $(lflags) -nodefaultlib:ucrt.lib +!endif + +dlllflags = $(lflags) -dll +conlflags = $(lflags) -subsystem:console +guilflags = $(lflags) -subsystem:windows + +# Libraries that are required for every image. +# Extensions should define any additional libraries with $(PRJ_LIBS) +winlibs = kernel32.lib advapi32.lib + +!if $(NEED_TK) +winlibs = $(winlibs) gdi32.lib user32.lib uxtheme.lib +!endif + +# Avoid 'unresolved external symbol __security_cookie' errors. +# c.f. http://support.microsoft.com/?id=894573 +!if "$(MACHINE)" == "AMD64" +!if $(VCVERSION) > 1399 && $(VCVERSION) < 1500 +winlibs = $(winlibs) bufferoverflowU.lib +!endif +!endif + +baselibs = $(winlibs) $(PRJ_LIBS) + +!if $(MSVCRT) && !($(DEBUG) && !$(UNCHECKED)) && $(VCVERSION) >= 1900 +baselibs = $(baselibs) ucrt.lib +!endif + +################################################################ +# 13. Define standard commands, common make targets and implicit rules + +CCPKGCMD = $(cc32) $(pkgcflags) -Fo$(TMP_DIR)^\ +CCAPPCMD = $(cc32) $(appcflags) -Fo$(TMP_DIR)^\ +CCSTUBSCMD = $(cc32) $(stubscflags) -Fo$(TMP_DIR)^\ + +LIBCMD = $(lib32) -nologo $(LINKERFLAGS) -out:$@ +DLLCMD = $(link32) $(dlllflags) -out:$@ $(baselibs) $(tcllibs) $(tklibs) + +CONEXECMD = $(link32) $(conlflags) -out:$@ $(baselibs) $(tcllibs) $(tklibs) +GUIEXECMD = $(link32) $(guilflags) -out:$@ $(baselibs) $(tcllibs) $(tklibs) +RESCMD = $(rc32) -fo $@ -r -i "$(GENERICDIR)" -i "$(TMP_DIR)" \ + $(TCL_INCLUDES) /DSTATIC_BUILD=$(STATIC_BUILD) \ + /DDEBUG=$(DEBUG) -d UNCHECKED=$(UNCHECKED) \ + /DCOMMAVERSION=$(RCCOMMAVERSION) \ + /DDOTVERSION=\"$(DOTVERSION)\" \ + /DVERSION=\"$(VERSION)\" \ + /DSUFX=\"$(SUFX)\" \ + /DPROJECT=\"$(PROJECT)\" \ + /DPRJLIBNAME=\"$(PRJLIBNAME)\" + +!ifndef DEFAULT_BUILD_TARGET +DEFAULT_BUILD_TARGET = $(PROJECT) +!endif + +default-target: $(DEFAULT_BUILD_TARGET) + +!if $(MULTIPLATFORM_INSTALL) +default-pkgindex: + @echo if {[package vsatisfies [package provide Tcl] 9.0]} { > $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PLATFORM_IDENTIFY) $(PRJLIBNAME9)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } else { >> $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PLATFORM_IDENTIFY) $(PRJLIBNAME8)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } >> $(OUT_DIR)\pkgIndex.tcl +!else +default-pkgindex: + @echo if {[package vsatisfies [package provide Tcl] 9.0]} { > $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PRJLIBNAME9)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } else { >> $(OUT_DIR)\pkgIndex.tcl + @echo package ifneeded $(PRJ_PACKAGE_TCLNAME) $(DOTVERSION) \ + [list load [file join $$dir $(PRJLIBNAME8)]] >> $(OUT_DIR)\pkgIndex.tcl + @echo } >> $(OUT_DIR)\pkgIndex.tcl +!endif + +default-pkgindex-tea: + @if exist $(ROOT)\pkgIndex.tcl.in nmakehlp -s << $(ROOT)\pkgIndex.tcl.in > $(OUT_DIR)\pkgIndex.tcl +@PACKAGE_VERSION@ $(DOTVERSION) +@PACKAGE_NAME@ $(PRJ_PACKAGE_TCLNAME) +@PACKAGE_TCLNAME@ $(PRJ_PACKAGE_TCLNAME) +@PKG_LIB_FILE@ $(PRJLIBNAME) +@PKG_LIB_FILE8@ $(PRJLIBNAME8) +@PKG_LIB_FILE9@ $(PRJLIBNAME9) +<< + +default-install: default-install-binaries default-install-libraries +!if $(SYMBOLS) +default-install: default-install-pdbs +!endif + +# Again to deal with historical brokenness, there is some confusion +# in terminlogy. For extensions, the "install-binaries" was used to +# locate target directory for *binary shared libraries* and thus +# the appropriate macro is LIB_INSTALL_DIR since BIN_INSTALL_DIR is +# for executables (exes). On the other hand the "install-libraries" +# target is for *scripts* and should have been called "install-scripts". +default-install-binaries: $(PRJLIB) + @echo Installing binaries to '$(LIB_INSTALL_DIR)' + @if not exist "$(LIB_INSTALL_DIR)" mkdir "$(LIB_INSTALL_DIR)" + @$(CPY) $(PRJLIB) "$(LIB_INSTALL_DIR)" >NUL + +# Alias for default-install-scripts +default-install-libraries: default-install-scripts + +default-install-scripts: $(OUT_DIR)\pkgIndex.tcl + @echo Installing libraries to '$(SCRIPT_INSTALL_DIR)' + @if not exist "$(SCRIPT_INSTALL_DIR)" mkdir "$(SCRIPT_INSTALL_DIR)" + @if exist $(LIBDIR) $(CPY) $(LIBDIR)\*.tcl "$(SCRIPT_INSTALL_DIR)" + @echo Installing package index in '$(SCRIPT_INSTALL_DIR)' + @$(CPY) $(OUT_DIR)\pkgIndex.tcl $(SCRIPT_INSTALL_DIR) + +default-install-stubs: + @echo Installing stubs library to '$(SCRIPT_INSTALL_DIR)' + @if not exist "$(SCRIPT_INSTALL_DIR)" mkdir "$(SCRIPT_INSTALL_DIR)" + @$(CPY) $(PRJSTUBLIB) "$(SCRIPT_INSTALL_DIR)" >NUL + +default-install-pdbs: + @echo Installing PDBs to '$(LIB_INSTALL_DIR)' + @if not exist "$(LIB_INSTALL_DIR)" mkdir "$(LIB_INSTALL_DIR)" + @$(CPY) "$(OUT_DIR)\*.pdb" "$(LIB_INSTALL_DIR)\" + +# "emacs font-lock highlighting fix + +default-install-docs-html: + @echo Installing documentation files to '$(DOC_INSTALL_DIR)' + @if not exist "$(DOC_INSTALL_DIR)" mkdir "$(DOC_INSTALL_DIR)" + @if exist $(DOCDIR) for %f in ("$(DOCDIR)\*.html" "$(DOCDIR)\*.css" "$(DOCDIR)\*.png") do @$(COPY) %f "$(DOC_INSTALL_DIR)" + +default-install-docs-n: + @echo Installing documentation files to '$(DOC_INSTALL_DIR)' + @if not exist "$(DOC_INSTALL_DIR)" mkdir "$(DOC_INSTALL_DIR)" + @if exist $(DOCDIR) for %f in ("$(DOCDIR)\*.n") do @$(COPY) %f "$(DOC_INSTALL_DIR)" + +default-install-demos: + @echo Installing demos to '$(DEMO_INSTALL_DIR)' + @if not exist "$(DEMO_INSTALL_DIR)" mkdir "$(DEMO_INSTALL_DIR)" + @if exist $(DEMODIR) $(CPYDIR) "$(DEMODIR)" "$(DEMO_INSTALL_DIR)" + +default-clean: + @echo Cleaning $(TMP_DIR)\* ... + @if exist $(TMP_DIR)\nul $(RMDIR) $(TMP_DIR) + @echo Cleaning $(WIN_DIR)\nmakehlp.obj, nmakehlp.exe ... + @if exist $(WIN_DIR)\nmakehlp.obj del $(WIN_DIR)\nmakehlp.obj + @if exist $(WIN_DIR)\nmakehlp.exe del $(WIN_DIR)\nmakehlp.exe + @if exist $(WIN_DIR)\nmakehlp.out del $(WIN_DIR)\nmakehlp.out + @echo Cleaning $(WIN_DIR)\nmhlp-out.txt ... + @if exist $(WIN_DIR)\nmhlp-out.txt del $(WIN_DIR)\nmhlp-out.txt + @echo Cleaning $(WIN_DIR)\_junk.pch ... + @if exist $(WIN_DIR)\_junk.pch del $(WIN_DIR)\_junk.pch + @echo Cleaning $(WIN_DIR)\vercl.x, vercl.i ... + @if exist $(WIN_DIR)\vercl.x del $(WIN_DIR)\vercl.x + @if exist $(WIN_DIR)\vercl.i del $(WIN_DIR)\vercl.i + @echo Cleaning $(WIN_DIR)\versions.vc, version.vc ... + @if exist $(WIN_DIR)\versions.vc del $(WIN_DIR)\versions.vc + @if exist $(WIN_DIR)\version.vc del $(WIN_DIR)\version.vc + +default-hose: default-clean + @echo Hosing $(OUT_DIR)\* ... + @if exist $(OUT_DIR)\nul $(RMDIR) $(OUT_DIR) + +# Only for backward compatibility +default-distclean: default-hose + +default-setup: + @if not exist $(OUT_DIR)\nul mkdir $(OUT_DIR) + @if not exist $(TMP_DIR)\nul mkdir $(TMP_DIR) + +!if "$(TESTPAT)" != "" +TESTFLAGS = $(TESTFLAGS) -file $(TESTPAT) +!endif + +default-test: default-setup $(PROJECT) + @set TCLLIBPATH=$(OUT_DIR:\=/) + @if exist $(LIBDIR) for %f in ("$(LIBDIR)\*.tcl") do @$(COPY) %f "$(OUT_DIR)" + cd "$(TESTDIR)" && $(DEBUGGER) $(TCLSH) all.tcl $(TESTFLAGS) + +default-shell: default-setup $(PROJECT) + @set TCLLIBPATH=$(OUT_DIR:\=/) + @if exist $(LIBDIR) for %f in ("$(LIBDIR)\*.tcl") do @$(COPY) %f "$(OUT_DIR)" + $(DEBUGGER) $(TCLSH) + +# Generation of Windows version resource +!ifdef RCFILE + +# Note: don't use $** in below rule because there may be other dependencies +# and only the "main" rc must be passed to the resource compiler +$(TMP_DIR)\$(PROJECT).res: $(RCDIR)\$(PROJECT).rc + $(RESCMD) $(RCDIR)\$(PROJECT).rc + +!else + +# If parent makefile has not defined a resource definition file, +# we will generate one from standard template. +$(TMP_DIR)\$(PROJECT).res: $(TMP_DIR)\$(PROJECT).rc + +$(TMP_DIR)\$(PROJECT).rc: + @$(COPY) << $(TMP_DIR)\$(PROJECT).rc +#include + +VS_VERSION_INFO VERSIONINFO + FILEVERSION COMMAVERSION + PRODUCTVERSION COMMAVERSION + FILEFLAGSMASK 0x3fL +#ifdef DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "Tcl extension " PROJECT + VALUE "OriginalFilename", PRJLIBNAME + VALUE "FileVersion", DOTVERSION + VALUE "ProductName", "Package " PROJECT " for Tcl" + VALUE "ProductVersion", DOTVERSION + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +<< + +!endif # ifdef RCFILE + +!ifndef DISABLE_IMPLICIT_RULES +DISABLE_IMPLICIT_RULES = 0 +!endif + +!if !$(DISABLE_IMPLICIT_RULES) +# Implicit rule definitions - only for building library objects. For stubs and +# main application, the makefile should define explicit rules. + +{$(ROOT)}.c{$(TMP_DIR)}.obj:: + $(CCPKGCMD) @<< +$< +<< + +{$(WIN_DIR)}.c{$(TMP_DIR)}.obj:: + $(CCPKGCMD) @<< +$< +<< + +{$(GENERICDIR)}.c{$(TMP_DIR)}.obj:: + $(CCPKGCMD) @<< +$< +<< + +{$(COMPATDIR)}.c{$(TMP_DIR)}.obj:: + $(CCPKGCMD) @<< +$< +<< + +{$(RCDIR)}.rc{$(TMP_DIR)}.res: + $(RESCMD) $< + +{$(WIN_DIR)}.rc{$(TMP_DIR)}.res: + $(RESCMD) $< + +{$(TMP_DIR)}.rc{$(TMP_DIR)}.res: + $(RESCMD) $< + +.SUFFIXES: +.SUFFIXES:.c .rc + +!endif + +################################################################ +# 14. Sanity check selected options against Tcl build options +# When building an extension, certain configuration options should +# match the ones used when Tcl was built. Here we check and +# warn on a mismatch. +!if !$(DOING_TCL) + +!if $(TCLINSTALL) # Building against an installed Tcl +!if exist("$(_TCLDIR)\lib\nmake\tcl.nmake") +TCLNMAKECONFIG = "$(_TCLDIR)\lib\nmake\tcl.nmake" +!endif +!else # !$(TCLINSTALL) - building against Tcl source +!if exist("$(_TCLDIR)\win\$(BUILDDIRTOP)\tcl.nmake") +TCLNMAKECONFIG = "$(_TCLDIR)\win\$(BUILDDIRTOP)\tcl.nmake" +!endif +!endif # TCLINSTALL + +!if $(CONFIG_CHECK) +!ifdef TCLNMAKECONFIG +!include $(TCLNMAKECONFIG) + +!if defined(CORE_MACHINE) && "$(CORE_MACHINE)" != "$(MACHINE)" +!error ERROR: Build target ($(MACHINE)) does not match the Tcl library architecture ($(CORE_MACHINE)). +!endif +!if $(TCL_VERSION) < 87 && defined(CORE_USE_THREAD_ALLOC) && $(CORE_USE_THREAD_ALLOC) != $(USE_THREAD_ALLOC) +!message WARNING: Value of USE_THREAD_ALLOC ($(USE_THREAD_ALLOC)) does not match its Tcl core value ($(CORE_USE_THREAD_ALLOC)). +!endif +!if defined(CORE_DEBUG) && $(CORE_DEBUG) != $(DEBUG) +!message WARNING: Value of DEBUG ($(DEBUG)) does not match its Tcl library configuration ($(DEBUG)). +!endif +!endif + +!endif # TCLNMAKECONFIG + +!endif # !$(DOING_TCL) + + +#---------------------------------------------------------- +# Display stats being used. +#---------------------------------------------------------- + +!if !$(DOING_TCL) +!message *** Building against Tcl at '$(_TCLDIR)' +!endif +!if !$(DOING_TK) && $(NEED_TK) +!message *** Building against Tk at '$(_TKDIR)' +!endif +!message *** Intermediate directory will be '$(TMP_DIR)' +!message *** Output directory will be '$(OUT_DIR)' +!message *** Installation, if selected, will be in '$(_INSTALLDIR)' +!message *** Suffix for binaries will be '$(SUFX)' +!message *** Compiler version $(VCVER). Target $(MACHINE), host $(NATIVE_ARCH). + +!endif # ifdef _RULES_VC diff --git a/vendor/tcl/win/svnmanifest.in b/vendor/tcl/win/svnmanifest.in new file mode 100644 index 00000000..18d2cad8 --- /dev/null +++ b/vendor/tcl/win/svnmanifest.in @@ -0,0 +1 @@ +svn-r \ No newline at end of file diff --git a/vendor/tcl/win/targets.vc b/vendor/tcl/win/targets.vc new file mode 100644 index 00000000..a7e44672 --- /dev/null +++ b/vendor/tcl/win/targets.vc @@ -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 diff --git a/vendor/tcl/win/tcl.dsp b/vendor/tcl/win/tcl.dsp new file mode 100644 index 00000000..8ec25ad7 --- /dev/null +++ b/vendor/tcl/win/tcl.dsp @@ -0,0 +1,1511 @@ +# Microsoft Developer Studio Project File - Name="tcl" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) External Target" 0x0106 + +CFG=tcl - Win32 Debug Static +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "tcl.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "tcl.mak" CFG="tcl - Win32 Debug Static" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "tcl - Win32 Release" (based on "Win32 (x86) External Target") +!MESSAGE "tcl - Win32 Debug" (based on "Win32 (x86) External Target") +!MESSAGE "tcl - Win32 Debug Static" (based on "Win32 (x86) External Target") +!MESSAGE "tcl - Win32 Release Static" (based on "Win32 (x86) External Target") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" + +!IF "$(CFG)" == "tcl - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release\tcl_Dynamic" +# PROP BASE Cmd_Line "nmake -nologo -f makefile.vc MSVCDIR=IDE" +# PROP BASE Rebuild_Opt "-a" +# PROP BASE Target_File "Release\tclsh90.exe" +# PROP BASE Bsc_Name "" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release\tcl_Dynamic" +# PROP Cmd_Line "nmake -nologo -f makefile.vc MSVCDIR=IDE" +# PROP Rebuild_Opt "clean release" +# PROP Target_File "Release\tclsh90t.exe" +# PROP Bsc_Name "" +# PROP Target_Dir "" + +!ELSEIF "$(CFG)" == "tcl - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug\tcl_Dynamic" +# PROP BASE Cmd_Line "nmake -nologo -f makefile.vc OPTS=symbols MSVCDIR=IDE" +# PROP BASE Rebuild_Opt "-a" +# PROP BASE Target_File "Debug\tclsh90g.exe" +# PROP BASE Bsc_Name "" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug\tcl_Dynamic" +# PROP Cmd_Line "nmake -nologo -f makefile.vc OPTS=threads,symbols MSVCDIR=IDE" +# PROP Rebuild_Opt "clean release" +# PROP Target_File "Debug\tclsh90tg.exe" +# PROP Bsc_Name "" +# PROP Target_Dir "" + +!ELSEIF "$(CFG)" == "tcl - Win32 Debug Static" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug\tcl_Static" +# PROP BASE Cmd_Line "nmake -nologo -f makefile.vc OPTS=symbols,static MSVCDIR=IDE" +# PROP BASE Rebuild_Opt "-a" +# PROP BASE Target_File "Debug\tclsh90sg.exe" +# PROP BASE Bsc_Name "" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug\tcl_Static" +# PROP Cmd_Line "nmake -nologo -f makefile.vc OPTS=symbols,static MSVCDIR=IDE" +# PROP Rebuild_Opt "-a" +# PROP Target_File "Debug\tclsh90sg.exe" +# PROP Bsc_Name "" +# PROP Target_Dir "" + +!ELSEIF "$(CFG)" == "tcl - Win32 Release Static" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release\tcl_Static" +# PROP BASE Cmd_Line "nmake -nologo -f makefile.vc OPTS=static MSVCDIR=IDE" +# PROP BASE Rebuild_Opt "-a" +# PROP BASE Target_File "Release\tclsh90s.exe" +# PROP BASE Bsc_Name "" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release\tcl_Static" +# PROP Cmd_Line "nmake -nologo -f makefile.vc OPTS=static MSVCDIR=IDE" +# PROP Rebuild_Opt "-a" +# PROP Target_File "Release\tclsh90s.exe" +# PROP Bsc_Name "" +# PROP Target_Dir "" + +!ENDIF + +# Begin Target + +# Name "tcl - Win32 Release" +# Name "tcl - Win32 Debug" +# Name "tcl - Win32 Debug Static" +# Name "tcl - Win32 Release Static" + +!IF "$(CFG)" == "tcl - Win32 Release" + +!ELSEIF "$(CFG)" == "tcl - Win32 Debug" + +!ELSEIF "$(CFG)" == "tcl - Win32 Debug Static" + +!ELSEIF "$(CFG)" == "tcl - Win32 Release Static" + +!ENDIF + +# Begin Group "compat" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\compat\dlfcn.h +# End Source File +# Begin Source File + +SOURCE=..\compat\gettod.c +# End Source File +# Begin Source File + +SOURCE=..\compat\limits.h +# End Source File +# Begin Source File + +SOURCE=..\compat\README +# End Source File +# Begin Source File + +SOURCE=..\compat\string.h +# End Source File +# End Group +# Begin Group "doc" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\doc\Access.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\AddErrInfo.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\after.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Alloc.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\AllowExc.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\append.n +# End Source File +# Begin Source File + +SOURCE=..\doc\AppInit.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\array.n +# End Source File +# Begin Source File + +SOURCE=..\doc\AssocData.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Async.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\BackgdErr.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Backslash.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\bgerror.n +# End Source File +# Begin Source File + +SOURCE=..\doc\binary.n +# End Source File +# Begin Source File + +SOURCE=..\doc\BoolObj.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\break.n +# End Source File +# Begin Source File + +SOURCE=..\doc\ByteArrObj.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CallDel.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\catch.n +# End Source File +# Begin Source File + +SOURCE=..\doc\cd.n +# End Source File +# Begin Source File + +SOURCE=..\doc\ChnlStack.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\clock.n +# End Source File +# Begin Source File + +SOURCE=..\doc\close.n +# End Source File +# Begin Source File + +SOURCE=..\doc\CmdCmplt.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Concat.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\concat.n +# End Source File +# Begin Source File + +SOURCE=..\doc\continue.n +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtChannel.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtChnlHdlr.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtCloseHdlr.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtCommand.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtFileHdlr.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtInterp.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtMathFnc.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtObjCmd.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtAlias.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtTimerHdlr.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\CrtTrace.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\dde.n +# End Source File +# Begin Source File + +SOURCE=..\doc\DetachPids.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\DoOneEvent.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\DoubleObj.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\DoWhenIdle.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\DString.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\DumpActiveMemory.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Encoding.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\encoding.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Environment.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\eof.n +# End Source File +# Begin Source File + +SOURCE=..\doc\error.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Eval.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\eval.n +# End Source File +# Begin Source File + +SOURCE=..\doc\exec.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Exit.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\exit.n +# End Source File +# Begin Source File + +SOURCE=..\doc\expr.n +# End Source File +# Begin Source File + +SOURCE=..\doc\ExprLong.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\ExprLongObj.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\fblocked.n +# End Source File +# Begin Source File + +SOURCE=..\doc\fconfigure.n +# End Source File +# Begin Source File + +SOURCE=..\doc\fcopy.n +# End Source File +# Begin Source File + +SOURCE=..\doc\file.n +# End Source File +# Begin Source File + +SOURCE=..\doc\fileevent.n +# End Source File +# Begin Source File + +SOURCE=..\doc\filename.n +# End Source File +# Begin Source File + +SOURCE=..\doc\FileSystem.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\FindExec.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\flush.n +# End Source File +# Begin Source File + +SOURCE=..\doc\for.n +# End Source File +# Begin Source File + +SOURCE=..\doc\foreach.n +# End Source File +# Begin Source File + +SOURCE=..\doc\format.n +# End Source File +# Begin Source File + +SOURCE=..\doc\GetCwd.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\GetHostName.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\GetIndex.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\GetInt.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\GetOpnFl.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\gets.n +# End Source File +# Begin Source File + +SOURCE=..\doc\GetStdChan.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\GetVersion.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\glob.n +# End Source File +# Begin Source File + +SOURCE=..\doc\global.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Hash.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\history.n +# End Source File +# Begin Source File + +SOURCE=..\doc\http.n +# End Source File +# Begin Source File + +SOURCE=..\doc\if.n +# End Source File +# Begin Source File + +SOURCE=..\doc\incr.n +# End Source File +# Begin Source File + +SOURCE=..\doc\info.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Init.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\InitStubs.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Interp.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\interp.n +# End Source File +# Begin Source File + +SOURCE=..\doc\IntObj.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\join.n +# End Source File +# Begin Source File + +SOURCE=..\doc\lappend.n +# End Source File +# Begin Source File + +SOURCE=..\doc\library.n +# End Source File +# Begin Source File + +SOURCE=..\doc\lindex.n +# End Source File +# Begin Source File + +SOURCE=..\doc\LinkVar.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\linsert.n +# End Source File +# Begin Source File + +SOURCE=..\doc\list.n +# End Source File +# Begin Source File + +SOURCE=..\doc\ListObj.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\llength.n +# End Source File +# Begin Source File + +SOURCE=..\doc\load.n +# End Source File +# Begin Source File + +SOURCE=..\doc\lrange.n +# End Source File +# Begin Source File + +SOURCE=..\doc\lreplace.n +# End Source File +# Begin Source File + +SOURCE=..\doc\lsearch.n +# End Source File +# Begin Source File + +SOURCE=..\doc\lsort.n +# End Source File +# Begin Source File + +SOURCE=..\doc\man.macros +# End Source File +# Begin Source File + +SOURCE=..\doc\memory.n +# End Source File +# Begin Source File + +SOURCE=..\doc\msgcat.n +# End Source File +# Begin Source File + +SOURCE=..\doc\namespace.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Notifier.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Object.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\ObjectType.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\open.n +# End Source File +# Begin Source File + +SOURCE=..\doc\OpenFileChnl.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\OpenTcp.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\package.n +# End Source File +# Begin Source File + +SOURCE=..\doc\packagens.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Panic.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\ParseCmd.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\pid.n +# End Source File +# Begin Source File + +SOURCE=..\doc\pkgMkIndex.n +# End Source File +# Begin Source File + +SOURCE=..\doc\PkgRequire.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Preserve.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\PrintDbl.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\proc.n +# End Source File +# Begin Source File + +SOURCE=..\doc\puts.n +# End Source File +# Begin Source File + +SOURCE=..\doc\pwd.n +# End Source File +# Begin Source File + +SOURCE=..\doc\re_syntax.n +# End Source File +# Begin Source File + +SOURCE=..\doc\read.n +# End Source File +# Begin Source File + +SOURCE=..\doc\RecEvalObj.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\RecordEval.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\RegExp.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\regexp.n +# End Source File +# Begin Source File + +SOURCE=..\doc\registry.n +# End Source File +# Begin Source File + +SOURCE=..\doc\regsub.n +# End Source File +# Begin Source File + +SOURCE=..\doc\rename.n +# End Source File +# Begin Source File + +SOURCE=..\doc\return.n +# End Source File +# Begin Source File + +SOURCE=..\doc\safe.n +# End Source File +# Begin Source File + +SOURCE=..\doc\SaveInterpState.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\scan.n +# End Source File +# Begin Source File + +SOURCE=..\doc\seek.n +# End Source File +# Begin Source File + +SOURCE=..\doc\set.n +# End Source File +# Begin Source File + +SOURCE=..\doc\SetErrno.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\SetRecLmt.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\SetResult.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\SetVar.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Signal.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Sleep.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\socket.n +# End Source File +# Begin Source File + +SOURCE=..\doc\source.n +# End Source File +# Begin Source File + +SOURCE=..\doc\SourceRCFile.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\split.n +# End Source File +# Begin Source File + +SOURCE=..\doc\SplitList.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\SplitPath.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\StaticLibrary.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\StdChannels.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\string.n +# End Source File +# Begin Source File + +SOURCE=..\doc\StringObj.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\StrMatch.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\subst.n +# End Source File +# Begin Source File + +SOURCE=..\doc\SubstObj.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\switch.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Tcl.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Tcl_Main.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\TCL_MEM_DEBUG.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\tclsh.1 +# End Source File +# Begin Source File + +SOURCE=..\doc\tcltest.n +# End Source File +# Begin Source File + +SOURCE=..\doc\tclvars.n +# End Source File +# Begin Source File + +SOURCE=..\doc\tell.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Thread.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\time.n +# End Source File +# Begin Source File + +SOURCE=..\doc\ToUpper.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\trace.n +# End Source File +# Begin Source File + +SOURCE=..\doc\TraceVar.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\Translate.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\UniCharIsAlpha.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\unknown.n +# End Source File +# Begin Source File + +SOURCE=..\doc\unset.n +# End Source File +# Begin Source File + +SOURCE=..\doc\update.n +# End Source File +# Begin Source File + +SOURCE=..\doc\uplevel.n +# End Source File +# Begin Source File + +SOURCE=..\doc\UpVar.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\upvar.n +# End Source File +# Begin Source File + +SOURCE=..\doc\Utf.3 +# End Source File +# Begin Source File + +SOURCE=..\doc\variable.n +# End Source File +# Begin Source File + +SOURCE=..\doc\vwait.n +# End Source File +# Begin Source File + +SOURCE=..\doc\while.n +# End Source File +# Begin Source File + +SOURCE=..\doc\WrongNumArgs.3 +# End Source File +# End Group +# Begin Group "generic" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\generic\README +# End Source File +# Begin Source File + +SOURCE=..\generic\regc_color.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regc_cvec.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regc_lex.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regc_locale.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regc_nfa.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regcomp.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regcustom.h +# End Source File +# Begin Source File + +SOURCE=..\generic\rege_dfa.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regerror.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regerrs.h +# End Source File +# Begin Source File + +SOURCE=..\generic\regex.h +# End Source File +# Begin Source File + +SOURCE=..\generic\regexec.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regfree.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regfronts.c +# End Source File +# Begin Source File + +SOURCE=..\generic\regguts.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tcl.decls +# End Source File +# Begin Source File + +SOURCE=..\generic\tcl.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclAlloc.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclAsync.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclBasic.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclBinary.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclCkalloc.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclClock.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclCmdAH.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclCmdIL.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclCmdMZ.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclCompCmds.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclCompExpr.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclCompile.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclCompile.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclDate.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclDecls.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclEncoding.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclEnv.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclEvent.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclExecute.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclFCmd.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclFileName.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclGet.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclGetDate.y +# End Source File +# Begin Source File + +SOURCE=..\generic\tclHash.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclHistory.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclIndexObj.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclInt.decls +# End Source File +# Begin Source File + +SOURCE=..\generic\tclInt.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclIntDecls.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclInterp.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclIntPlatDecls.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclIO.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclIO.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclIOCmd.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclIOGT.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclIOSock.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclIOUtil.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclLink.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclListObj.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclLiteral.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclLoad.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclLoadNone.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclMain.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclNamesp.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclNotify.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclObj.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclPanic.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclParse.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclPipe.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclPkg.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclPlatDecls.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclPort.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclPosixStr.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclPreserve.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclProc.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclProcess.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclRegexp.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclRegexp.h +# End Source File +# Begin Source File + +SOURCE=..\generic\tclResolve.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclResult.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclScan.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclStringObj.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclStubInit.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclStubLib.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclStubCall.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclStubLibTbl.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclOOStubLib.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclTomMathStubLib.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclTest.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclTestObj.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclTestProcBodyObj.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclThread.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclThreadJoin.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclThreadTest.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclTimer.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclUniData.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclUtf.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclUtil.c +# End Source File +# Begin Source File + +SOURCE=..\generic\tclVar.c +# End Source File +# End Group +# Begin Group "library" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\library\auto.tcl +# End Source File +# Begin Source File + +SOURCE=..\library\history.tcl +# End Source File +# Begin Source File + +SOURCE=..\library\init.tcl +# End Source File +# Begin Source File + +SOURCE=..\library\ldAout.tcl +# End Source File +# Begin Source File + +SOURCE=..\library\package.tcl +# End Source File +# Begin Source File + +SOURCE=..\library\parray.tcl +# End Source File +# Begin Source File + +SOURCE=..\library\safe.tcl +# End Source File +# Begin Source File + +SOURCE=..\library\tclIndex +# End Source File +# Begin Source File + +SOURCE=..\library\word.tcl +# End Source File +# End Group +# Begin Group "mac" + +# PROP Default_Filter "" +# End Group +# Begin Group "tests" + +# PROP Default_Filter "" +# End Group +# Begin Group "tools" + +# PROP Default_Filter "" +# End Group +# Begin Group "unix" + +# PROP Default_Filter "" +# End Group +# Begin Group "win" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\aclocal.m4 +# End Source File +# Begin Source File + +SOURCE=.\cat.c +# End Source File +# Begin Source File + +SOURCE=.\configure +# End Source File +# Begin Source File + +SOURCE=.\configure.ac +# End Source File +# Begin Source File + +SOURCE=.\Makefile.in +# End Source File +# Begin Source File + +SOURCE=.\makefile.vc +# End Source File +# Begin Source File + +SOURCE=.\mkd.bat +# End Source File +# Begin Source File + +SOURCE=.\README +# End Source File +# Begin Source File + +SOURCE=.\README.binary +# End Source File +# Begin Source File + +SOURCE=.\rmd.bat +# End Source File +# Begin Source File + +SOURCE=.\rules.vc +# End Source File +# Begin Source File + +SOURCE=.\tcl.m4 +# End Source File +# Begin Source File + +SOURCE=.\tcl.rc +# End Source File +# Begin Source File + +SOURCE=.\tclAppInit.c +# End Source File +# Begin Source File + +SOURCE=.\tclConfig.sh.in +# End Source File +# Begin Source File + +SOURCE=.\tclsh.ico +# End Source File +# Begin Source File + +SOURCE=.\tclsh.rc +# End Source File +# Begin Source File + +SOURCE=.\tclWin32Dll.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinChan.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinConsole.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinDde.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinError.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinFCmd.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinFile.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinInit.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinInt.h +# End Source File +# Begin Source File + +SOURCE=.\tclWinLoad.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinNotify.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinPanic.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinPipe.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinPort.h +# End Source File +# Begin Source File + +SOURCE=.\tclWinReg.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinSerial.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinSock.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinTest.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinThrd.c +# End Source File +# Begin Source File + +SOURCE=.\tclWinTime.c +# End Source File +# End Group +# End Target +# End Project diff --git a/vendor/tcl/win/tcl.dsw b/vendor/tcl/win/tcl.dsw new file mode 100644 index 00000000..fa93b000 --- /dev/null +++ b/vendor/tcl/win/tcl.dsw @@ -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> +{{{ +}}} + +############################################################################### + diff --git a/vendor/tcl/win/tcl.m4 b/vendor/tcl/win/tcl.m4 new file mode 100644 index 00000000..2ce4aedd --- /dev/null +++ b/vendor/tcl/win/tcl.m4 @@ -0,0 +1,1272 @@ +#------------------------------------------------------------------------ +# SC_PATH_TCLCONFIG -- +# +# Locate the tclConfig.sh file and perform a sanity check on +# the Tcl compile flags +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --with-tcl=... +# +# Defines the following vars: +# TCL_BIN_DIR Full path to the directory containing +# the tclConfig.sh file +#------------------------------------------------------------------------ + +AC_DEFUN([SC_PATH_TCLCONFIG], [ + # + # Ok, lets find the tcl configuration + # First, look for one uninstalled. + # the alternative search directory is invoked by --with-tcl + # + + if test x"${no_tcl}" = x ; then + # we reset no_tcl in case something fails here + no_tcl=true + AC_ARG_WITH(tcl, + AS_HELP_STRING([--with-tcl], + [directory containing tcl configuration (tclConfig.sh)]), + [with_tclconfig="${withval}"]) + AC_MSG_CHECKING([for Tcl configuration]) + AC_CACHE_VAL(ac_cv_c_tclconfig,[ + + # First check to see if --with-tcl was specified. + if test x"${with_tclconfig}" != x ; then + case "${with_tclconfig}" in + */tclConfig.sh ) + if test -f "${with_tclconfig}"; then + AC_MSG_WARN([--with-tcl argument should refer to directory containing tclConfig.sh, not to tclConfig.sh itself]) + with_tclconfig="`echo "${with_tclconfig}" | sed 's!/tclConfig\.sh$!!'`" + fi ;; + esac + if test -f "${with_tclconfig}/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd "${with_tclconfig}"; pwd)`" + else + AC_MSG_ERROR([${with_tclconfig} directory doesn't contain tclConfig.sh]) + fi + fi + + # then check for a private Tcl installation + if test x"${ac_cv_c_tclconfig}" = x ; then + for i in \ + ../tcl \ + `ls -dr ../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../tcl[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../tcl[[8-9]].[[0-9]]* 2>/dev/null` \ + ../../tcl \ + `ls -dr ../../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../../tcl[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../tcl[[8-9]].[[0-9]]* 2>/dev/null` \ + ../../../tcl \ + `ls -dr ../../../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../../../tcl[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../../tcl[[8-9]].[[0-9]]* 2>/dev/null` ; do + if test -f "$i/win/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/win; pwd)`" + break + fi + done + fi + + # check in a few common install locations + if test x"${ac_cv_c_tclconfig}" = x ; then + for i in `ls -d ${libdir} 2>/dev/null` \ + `ls -d ${exec_prefix}/lib 2>/dev/null` \ + `ls -d ${prefix}/lib 2>/dev/null` \ + `ls -d /cygdrive/c/Tcl/lib 2>/dev/null` \ + `ls -d /cygdrive/c/Progra~1/Tcl/lib 2>/dev/null` \ + `ls -d /c/Tcl/lib 2>/dev/null` \ + `ls -d /c/Progra~1/Tcl/lib 2>/dev/null` \ + `ls -d C:/Tcl/lib 2>/dev/null` \ + `ls -d C:/Progra~1/Tcl/lib 2>/dev/null` \ + ; do + if test -f "$i/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i; pwd)`" + break + fi + done + fi + + # check in a few other private locations + if test x"${ac_cv_c_tclconfig}" = x ; then + for i in \ + ${srcdir}/../tcl \ + `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ${srcdir}/../tcl[[8-9]].[[0-9]]* 2>/dev/null` ; do + if test -f "$i/win/tclConfig.sh" ; then + ac_cv_c_tclconfig="`(cd $i/win; pwd)`" + break + fi + done + fi + ]) + + if test x"${ac_cv_c_tclconfig}" = x ; then + TCL_BIN_DIR="# no Tcl configs found" + AC_MSG_ERROR([Can't find Tcl configuration definitions. Use --with-tcl to specify a directory containing tclConfig.sh]) + else + no_tcl= + TCL_BIN_DIR="${ac_cv_c_tclconfig}" + AC_MSG_RESULT([found ${TCL_BIN_DIR}/tclConfig.sh]) + fi + fi +]) + +#------------------------------------------------------------------------ +# SC_PATH_TKCONFIG -- +# +# Locate the tkConfig.sh file +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --with-tk=... +# +# Defines the following vars: +# TK_BIN_DIR Full path to the directory containing +# the tkConfig.sh file +#------------------------------------------------------------------------ + +AC_DEFUN([SC_PATH_TKCONFIG], [ + # + # Ok, lets find the tk configuration + # First, look for one uninstalled. + # the alternative search directory is invoked by --with-tk + # + + if test x"${no_tk}" = x ; then + # we reset no_tk in case something fails here + no_tk=true + AC_ARG_WITH(tk, + AS_HELP_STRING([--with-tk], + [directory containing tk configuration (tkConfig.sh)]), + [with_tkconfig="${withval}"]) + AC_MSG_CHECKING([for Tk configuration]) + AC_CACHE_VAL(ac_cv_c_tkconfig,[ + + # First check to see if --with-tkconfig was specified. + if test x"${with_tkconfig}" != x ; then + case "${with_tkconfig}" in + */tkConfig.sh ) + if test -f "${with_tkconfig}"; then + AC_MSG_WARN([--with-tk argument should refer to directory containing tkConfig.sh, not to tkConfig.sh itself]) + with_tkconfig="`echo "${with_tkconfig}" | sed 's!/tkConfig\.sh$!!'`" + fi ;; + esac + if test -f "${with_tkconfig}/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd "${with_tkconfig}"; pwd)`" + else + AC_MSG_ERROR([${with_tkconfig} directory doesn't contain tkConfig.sh]) + fi + fi + + # then check for a private Tk library + if test x"${ac_cv_c_tkconfig}" = x ; then + for i in \ + ../tk \ + `ls -dr ../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../tk[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../tk[[8-9]].[[0-9]]* 2>/dev/null` \ + ../../tk \ + `ls -dr ../../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../../tk[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../tk[[8-9]].[[0-9]]* 2>/dev/null` \ + ../../../tk \ + `ls -dr ../../../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ../../../tk[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ../../../tk[[8-9]].[[0-9]]* 2>/dev/null` ; do + if test -f "$i/win/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i/win; pwd)`" + break + fi + done + fi + + # check in a few common install locations + if test x"${ac_cv_c_tkconfig}" = x ; then + for i in `ls -d ${libdir} 2>/dev/null` \ + `ls -d ${exec_prefix}/lib 2>/dev/null` \ + `ls -d ${prefix}/lib 2>/dev/null` \ + `ls -d /cygdrive/c/Tcl/lib 2>/dev/null` \ + `ls -d /cygdrive/c/Progra~1/Tcl/lib 2>/dev/null` \ + `ls -d /c/Tcl/lib 2>/dev/null` \ + `ls -d /c/Progra~1/Tcl/lib 2>/dev/null` \ + `ls -d C:/Tcl/lib 2>/dev/null` \ + `ls -d C:/Progra~1/Tcl/lib 2>/dev/null` \ + ; do + if test -f "$i/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i; pwd)`" + break + fi + done + fi + + # check in a few other private locations + if test x"${ac_cv_c_tkconfig}" = x ; then + for i in \ + ${srcdir}/../tk \ + `ls -dr ${srcdir}/../tk[[8-9]].[[0-9]].[[0-9]]* 2>/dev/null` \ + `ls -dr ${srcdir}/../tk[[8-9]].[[0-9]] 2>/dev/null` \ + `ls -dr ${srcdir}/../tk[[8-9]].[[0-9]]* 2>/dev/null` ; do + if test -f "$i/win/tkConfig.sh" ; then + ac_cv_c_tkconfig="`(cd $i/win; pwd)`" + break + fi + done + fi + ]) + + if test x"${ac_cv_c_tkconfig}" = x ; then + TK_BIN_DIR="# no Tk configs found" + AC_MSG_ERROR([Can't find Tk configuration definitions. Use --with-tk to specify a directory containing tkConfig.sh]) + else + no_tk= + TK_BIN_DIR="${ac_cv_c_tkconfig}" + AC_MSG_RESULT([found ${TK_BIN_DIR}/tkConfig.sh]) + fi + fi +]) + +#------------------------------------------------------------------------ +# SC_LOAD_TCLCONFIG -- +# +# Load the tclConfig.sh file. +# +# Arguments: +# +# Requires the following vars to be set: +# TCL_BIN_DIR +# +# Results: +# +# Substitutes the following vars: +# TCL_BIN_DIR +# TCL_SRC_DIR +# TCL_LIB_FILE +# +#------------------------------------------------------------------------ + +AC_DEFUN([SC_LOAD_TCLCONFIG], [ + AC_MSG_CHECKING([for existence of ${TCL_BIN_DIR}/tclConfig.sh]) + + if test -f "${TCL_BIN_DIR}/tclConfig.sh" ; then + AC_MSG_RESULT([loading]) + . "${TCL_BIN_DIR}/tclConfig.sh" + else + AC_MSG_RESULT([could not find ${TCL_BIN_DIR}/tclConfig.sh]) + fi + + # + # If the TCL_BIN_DIR is the build directory (not the install directory), + # then set the common variable name to the value of the build variables. + # For example, the variable TCL_LIB_SPEC will be set to the value + # of TCL_BUILD_LIB_SPEC. An extension should make use of TCL_LIB_SPEC + # instead of TCL_BUILD_LIB_SPEC since it will work with both an + # installed and uninstalled version of Tcl. + # + + if test -f $TCL_BIN_DIR/Makefile ; then + TCL_LIB_SPEC=${TCL_BUILD_LIB_SPEC} + TCL_STUB_LIB_SPEC=${TCL_BUILD_STUB_LIB_SPEC} + TCL_STUB_LIB_PATH=${TCL_BUILD_STUB_LIB_PATH} + fi + + eval "TCL_STUB_LIB_FILE=\"${TCL_STUB_LIB_FILE}\"" + eval "TCL_STUB_LIB_FLAG=\"${TCL_STUB_LIB_FLAG}\"" + eval "TCL_STUB_LIB_SPEC=\"${TCL_STUB_LIB_SPEC}\"" + + AC_SUBST(TCL_VERSION) + AC_SUBST(TCL_BIN_DIR) + AC_SUBST(TCL_SRC_DIR) + + AC_SUBST(TCL_LIB_FILE) + AC_SUBST(TCL_LIB_FLAG) + 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_DEFS) +]) + +#------------------------------------------------------------------------ +# SC_LOAD_TKCONFIG -- +# +# Load the tkConfig.sh file +# +# Arguments: +# +# Requires the following vars to be set: +# TK_BIN_DIR +# +# Results: +# +# Sets the following vars that should be in tkConfig.sh: +# TK_BIN_DIR +#------------------------------------------------------------------------ + +AC_DEFUN([SC_LOAD_TKCONFIG], [ + AC_MSG_CHECKING([for existence of ${TK_BIN_DIR}/tkConfig.sh]) + + if test -f "${TK_BIN_DIR}/tkConfig.sh" ; then + AC_MSG_RESULT([loading]) + . "${TK_BIN_DIR}/tkConfig.sh" + else + AC_MSG_RESULT([could not find ${TK_BIN_DIR}/tkConfig.sh]) + fi + + + AC_SUBST(TK_BIN_DIR) + AC_SUBST(TK_SRC_DIR) + AC_SUBST(TK_LIB_FILE) +]) + +#------------------------------------------------------------------------ +# SC_ENABLE_SHARED -- +# +# Allows the building of shared libraries +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --enable-shared=yes|no +# +# Defines the following vars: +# STATIC_BUILD Used for building import/export libraries +# on Windows. +# +# Sets the following vars: +# SHARED_BUILD Value of 1 or 0 +#------------------------------------------------------------------------ + +AC_DEFUN([SC_ENABLE_SHARED], [ + AC_MSG_CHECKING([how to build libraries]) + AC_ARG_ENABLE(shared, + [ --enable-shared build and link with shared libraries (default: on)], + [tcl_ok=$enableval], [tcl_ok=yes]) + if test "$tcl_ok" = "yes" ; then + AC_MSG_RESULT([shared]) + SHARED_BUILD=1 + else + AC_MSG_RESULT([static]) + SHARED_BUILD=0 + AC_DEFINE(STATIC_BUILD, 1, [Is this a static build?]) + fi + AC_SUBST(SHARED_BUILD) +]) + +#------------------------------------------------------------------------ +# SC_ENABLE_SYMBOLS -- +# +# Specify if debugging symbols should be used. +# Memory (TCL_MEM_DEBUG) and compile (TCL_COMPILE_DEBUG) debugging +# can also be enabled. +# +# Arguments: +# none +# +# Requires the following vars to be set in the Makefile: +# CFLAGS_DEBUG +# CFLAGS_OPTIMIZE +# +# Results: +# +# Adds the following arguments to configure: +# --enable-symbols +# +# Defines the following vars: +# CFLAGS_DEFAULT Sets to $(CFLAGS_DEBUG) if true +# Sets to $(CFLAGS_OPTIMIZE) if false +# LDFLAGS_DEFAULT Sets to $(LDFLAGS_DEBUG) if true +# Sets to $(LDFLAGS_OPTIMIZE) if false +# +#------------------------------------------------------------------------ + +AC_DEFUN([SC_ENABLE_SYMBOLS], [ + AC_MSG_CHECKING([for build with symbols]) + AC_ARG_ENABLE(symbols, [ --enable-symbols build with debugging symbols (default: off)], [tcl_ok=$enableval], [tcl_ok=no]) +# FIXME: Currently, LDFLAGS_DEFAULT is not used, it should work like CFLAGS_DEFAULT. + if test "$tcl_ok" = "no"; then + CFLAGS_DEFAULT='$(CFLAGS_OPTIMIZE)' + LDFLAGS_DEFAULT='$(LDFLAGS_OPTIMIZE)' + AC_DEFINE(NDEBUG, 1, [Is no debugging enabled?]) + AC_MSG_RESULT([no]) + + AC_DEFINE(TCL_CFG_OPTIMIZED) + else + CFLAGS_DEFAULT='$(CFLAGS_DEBUG)' + LDFLAGS_DEFAULT='$(LDFLAGS_DEBUG)' + if test "$tcl_ok" = "yes"; then + AC_MSG_RESULT([yes (standard debugging)]) + fi + fi + AC_SUBST(CFLAGS_DEFAULT) + AC_SUBST(LDFLAGS_DEFAULT) + + if test "$tcl_ok" = "mem" -o "$tcl_ok" = "all"; then + AC_DEFINE(TCL_MEM_DEBUG, 1, [Is memory debugging enabled?]) + fi + + if test "$tcl_ok" = "compile" -o "$tcl_ok" = "all"; then + AC_DEFINE(TCL_COMPILE_DEBUG, 1, [Is bytecode debugging enabled?]) + AC_DEFINE(TCL_COMPILE_STATS, 1, [Are bytecode statistics enabled?]) + fi + + if test "$tcl_ok" != "yes" -a "$tcl_ok" != "no"; then + if test "$tcl_ok" = "all"; then + AC_MSG_RESULT([enabled symbols mem compile debugging]) + else + AC_MSG_RESULT([enabled $tcl_ok debugging]) + fi + fi +]) + +#-------------------------------------------------------------------- +# SC_CONFIG_CFLAGS +# +# Try to determine the proper flags to pass to the compiler +# for building shared libraries and other such nonsense. +# +# NOTE: The backslashes in quotes below are substituted twice +# due to the fact that they are in a macro and then inlined +# in the final configure script. +# +# Arguments: +# none +# +# Results: +# +# Can the following vars: +# EXTRA_CFLAGS +# CFLAGS_DEBUG +# CFLAGS_OPTIMIZE +# CFLAGS_WARNING +# CFLAGS_NOLTO +# LDFLAGS_DEBUG +# LDFLAGS_OPTIMIZE +# LDFLAGS_CONSOLE +# LDFLAGS_WINDOW +# CC_OBJNAME +# CC_EXENAME +# CYGPATH +# STLIB_LD +# SHLIB_LD +# SHLIB_LD_LIBS +# LIBS +# AR +# RC +# RES +# +# MAKE_LIB +# MAKE_STUB_LIB +# MAKE_EXE +# MAKE_DLL +# +# LIBSUFFIX +# LIBFLAGSUFFIX +# LIBPREFIX +# LIBRARIES +# EXESUFFIX +# DLLSUFFIX +# +#-------------------------------------------------------------------- + +AC_DEFUN([SC_CONFIG_CFLAGS], [ + + # Step 0: Enable 64 bit support? + + AC_MSG_CHECKING([if 64bit support is requested]) + AC_ARG_ENABLE(64bit,[ --enable-64bit enable 64bit support (where applicable)], [do64bit=$enableval], [do64bit=no]) + AC_MSG_RESULT($do64bit) + + # Set some defaults (may get changed below) + EXTRA_CFLAGS="" + AC_DEFINE(MODULE_SCOPE, [extern], [No need to mark inidividual symbols as hidden]) + + AC_CHECK_PROG(CYGPATH, cygpath, cygpath -m, echo) + AC_CHECK_PROG(WINE, wine, wine,) + + SHLIB_SUFFIX=".dll" + + # MACHINE is IX86 for LINK, but this is used by the manifest, + # which requires x86|amd64|arm64|ia64. + MACHINE="X86" + + if test "$GCC" = "yes"; then + + AC_CACHE_CHECK(for cross-compile version of gcc, + ac_cv_cross, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #ifndef _WIN32 + #error cross-compiler + #endif + ]], [[]])], + [ac_cv_cross=no], + [ac_cv_cross=yes]) + ) + + if test "$ac_cv_cross" = "yes"; then + case "$do64bit" in + amd64|x64|yes) + CC="x86_64-w64-mingw32-${CC}" + LD="x86_64-w64-mingw32-ld" + AR="x86_64-w64-mingw32-ar" + RANLIB="x86_64-w64-mingw32-ranlib" + RC="x86_64-w64-mingw32-windres" + ;; + arm64|aarch64) + CC="aarch64-w64-mingw32-${CC}" + LD="aarch64-w64-mingw32-ld" + AR="aarch64-w64-mingw32-ar" + RANLIB="aarch64-w64-mingw32-ranlib" + RC="aarch64-w64-mingw32-windres" + ;; + *) + CC="i686-w64-mingw32-${CC}" + LD="i686-w64-mingw32-ld" + AR="i686-w64-mingw32-ar" + RANLIB="i686-w64-mingw32-ranlib" + RC="i686-w64-mingw32-windres" + ;; + esac + fi + fi + + # Check for a bug in gcc's windres that causes the + # compile to fail when a Windows native path is + # passed into windres. The mingw toolchain requires + # Windows native paths while Cygwin should work + # with both. Avoid the bug by passing a POSIX + # path when using the Cygwin toolchain. + + if test "$GCC" = "yes" && test "$CYGPATH" != "echo" ; then + conftest=/tmp/conftest.rc + echo "STRINGTABLE BEGIN" > $conftest + echo "101 \"name\"" >> $conftest + echo "END" >> $conftest + + AC_MSG_CHECKING([for Windows native path bug in windres]) + cyg_conftest=`$CYGPATH $conftest` + if AC_TRY_COMMAND($RC -o conftest.res.o $cyg_conftest) ; then + AC_MSG_RESULT([no]) + else + AC_MSG_RESULT([yes]) + CYGPATH=echo + fi + conftest= + cyg_conftest= + fi + + if test "$CYGPATH" = "echo"; then + DEPARG='"$<"' + else + DEPARG='"$(shell $(CYGPATH) $<)"' + fi + + # set various compiler flags depending on whether we are using gcc or cl + + if test "${GCC}" = "yes" ; then + extra_cflags="-pipe" + extra_ldflags="-pipe -static-libgcc -municode" + AC_CACHE_CHECK(for mingw32 version of gcc, + ac_cv_win32, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #ifdef _WIN32 + #error win32 + #endif + ]], [[]])], + [ac_cv_win32=no], + [ac_cv_win32=yes]) + ) + if test "$ac_cv_win32" != "yes"; then + AC_MSG_ERROR([${CC} cannot produce win32 executables.]) + fi + if test "$do64bit" != "arm64" -a "$do64bit" != "aarch64"; then + extra_cflags="$extra_cflags -DHAVE_CPUID=1" + fi + + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -fno-lto" + AC_CACHE_CHECK(for working -fno-lto, + ac_cv_nolto, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])], + [ac_cv_nolto=yes], + [ac_cv_nolto=no]) + ) + CFLAGS=$hold_cflags + if test "$ac_cv_nolto" = "yes" ; then + CFLAGS_NOLTO="-fno-lto" + else + CFLAGS_NOLTO="" + fi + + AC_CACHE_CHECK([if the linker understands --disable-high-entropy-va], + tcl_cv_ld_high_entropy, [ + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Wl,--disable-high-entropy-va" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[tcl_cv_ld_high_entropy=yes],[tcl_cv_ld_high_entropy=no]) + CFLAGS=$hold_cflags]) + if test $tcl_cv_ld_high_entropy = yes; then + extra_ldflags="$extra_ldflags -Wl,--disable-high-entropy-va" + fi + + AC_CACHE_CHECK([if the compiler understands -finput-charset], + tcl_cv_cc_input_charset, [ + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -finput-charset=UTF-8" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],[tcl_cv_cc_input_charset=yes],[tcl_cv_cc_input_charset=no]) + CFLAGS=$hold_cflags]) + if test $tcl_cv_cc_input_charset = yes; then + extra_cflags="$extra_cflags -finput-charset=UTF-8" + fi + fi + + hold_cflags=$CFLAGS; CFLAGS="$CFLAGS -Wl,--enable-auto-image-base" + AC_CACHE_CHECK(for working --enable-auto-image-base, + ac_cv_enable_auto_image_base, + AC_LINK_IFELSE([AC_LANG_PROGRAM([])], + [ac_cv_enable_auto_image_base=yes], + [ac_cv_enable_auto_image_base=no]) + ) + CFLAGS=$hold_cflags + if test "$ac_cv_enable_auto_image_base" = "yes" ; then + extra_ldflags="$extra_ldflags -Wl,--enable-auto-image-base" + fi + + AC_MSG_CHECKING([compiler flags]) + if test "${GCC}" = "yes" ; then + SHLIB_LD="" + SHLIB_LD_LIBS='${LIBS}' + LIBS="-lnetapi32 -lkernel32 -luser32 -ladvapi32 -luserenv -lws2_32" + # mingw needs to link ole32 and oleaut32 for [send], but MSVC doesn't + LIBS_GUI="-lgdi32 -lcomdlg32 -limm32 -lcomctl32 -lshell32 -luuid -lole32 -loleaut32 -lwinspool" + STLIB_LD='${AR} cr' + RC_OUT=-o + RC_TYPE= + RC_INCLUDE=--include + RC_DEFINE=--define + RES=res.o + MAKE_LIB="\${STLIB_LD} \[$]@" + MAKE_STUB_LIB="\${STLIB_LD} \[$]@" + POST_MAKE_LIB="\${RANLIB} \[$]@" + MAKE_EXE="\${CC} -o \[$]@" + LIBPREFIX="lib" + + if test "${SHARED_BUILD}" = "0" ; then + # static + AC_MSG_RESULT([using static flags]) + runtime= + LIBRARIES="\${STATIC_LIBRARIES}" + EXESUFFIX="s.exe" + else + # dynamic + AC_MSG_RESULT([using shared flags]) + + # ad-hoc check to see if CC supports -shared. + if "${CC}" -shared 2>&1 | egrep ': -shared not supported' >/dev/null; then + AC_MSG_ERROR([${CC} does not support the -shared option. + You will need to upgrade to a newer version of the toolchain.]) + fi + + runtime= + # Add SHLIB_LD_LIBS to the Make rule, not here. + + EXESUFFIX=".exe" + LIBRARIES="\${SHARED_LIBRARIES}" + fi + # Link with gcc since ld does not link to default libs like + # -luser32 and -lmsvcrt by default. + SHLIB_LD='${CC} -shared' + SHLIB_LD_LIBS='${LIBS}' + MAKE_DLL="\${SHLIB_LD} \$(LDFLAGS) -o \[$]@ ${extra_ldflags} \ + -Wl,--out-implib,\$(patsubst %.dll,lib%.dll.a,\[$]@)" + # DLLSUFFIX is separate because it is the building block for + # users of tclConfig.sh that may build shared or static. + DLLSUFFIX=".dll" + LIBSUFFIX=".a" + LIBFLAGSUFFIX="" + SHLIB_SUFFIX=.dll + + EXTRA_CFLAGS="${extra_cflags}" + + CFLAGS_DEBUG=-g + CFLAGS_OPTIMIZE="-O2 -fomit-frame-pointer" + CFLAGS_WARNING="-Wall -Wextra -Wshadow -Wundef -Wwrite-strings -Wpointer-arith" + LDFLAGS_DEBUG= + LDFLAGS_OPTIMIZE= + + case "${CC}" in + *++) + CFLAGS_WARNING="${CFLAGS_WARNING} -Wno-format" + ;; + *) + CFLAGS_WARNING="${CFLAGS_WARNING} -Wc++-compat -fextended-identifiers" + ;; + esac + + # Specify the CC output file names based on the target name + CC_OBJNAME="-o \[$]@" + CC_EXENAME="-o \[$]@" + + # Specify linker flags depending on the type of app being + # built -- Console vs. Window. + # + # ORIGINAL COMMENT: + # We need to pass -e _WinMain@16 so that ld will use + # WinMain() instead of main() as the entry point. We can't + # use autoconf to check for this case since it would need + # to run an executable and that does not work when + # cross compiling. Remove this -e workaround once we + # require a gcc that does not have this bug. + # + # MK NOTE: Tk should use a different mechanism. This causes + # interesting problems, such as wish dying at startup. + #LDFLAGS_WINDOW="-mwindows -e _WinMain@16 ${extra_ldflags}" + LDFLAGS_CONSOLE="-mconsole ${extra_ldflags}" + LDFLAGS_WINDOW="-mwindows ${extra_ldflags}" + + case "$do64bit" in + amd64|x64|yes) + MACHINE="AMD64" ; # assume AMD64 as default 64-bit build + AC_MSG_RESULT([ Using 64-bit $MACHINE mode]) + ;; + arm64|aarch64) + MACHINE="ARM64" + AC_MSG_RESULT([ Using ARM64 $MACHINE mode]) + ;; + ia64) + MACHINE="IA64" + AC_MSG_RESULT([ Using IA64 $MACHINE mode]) + ;; + *) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #ifndef _WIN64 + #error 32-bit + #endif + ]], [[]])], + [tcl_win_64bit=yes], + [tcl_win_64bit=no] + ) + if test "$tcl_win_64bit" = "yes" ; then + do64bit=amd64 + MACHINE="AMD64" + AC_MSG_RESULT([ Using 64-bit $MACHINE mode]) + fi + ;; + esac + else + if test "${SHARED_BUILD}" = "0" ; then + # static + AC_MSG_RESULT([using static flags]) + runtime=-MT + LIBRARIES="\${STATIC_LIBRARIES}" + EXESUFFIX="s.exe" + else + # dynamic + AC_MSG_RESULT([using shared flags]) + runtime=-MD + # Add SHLIB_LD_LIBS to the Make rule, not here. + LIBRARIES="\${SHARED_LIBRARIES}" + EXESUFFIX=".exe" + case "x`echo \${VisualStudioVersion}`" in + x1[[4-9]]*) + lflags="${lflags} -nodefaultlib:ucrt.lib" + ;; + *) + ;; + esac + fi + MAKE_DLL="\${SHLIB_LD} \$(LDFLAGS) -out:\[$]@" + # DLLSUFFIX is separate because it is the building block for + # users of tclConfig.sh that may build shared or static. + DLLSUFFIX=".dll" + LIBSUFFIX=".lib" + LIBFLAGSUFFIX="" + + if test "$do64bit" != "no" ; then + case "$do64bit" in + amd64|x64|yes) + MACHINE="AMD64" ; # assume AMD64 as default 64-bit build + ;; + arm64|aarch64) + MACHINE="ARM64" + ;; + ia64) + MACHINE="IA64" + ;; + esac + AC_MSG_RESULT([ Using 64-bit $MACHINE mode]) + fi + + LIBS="netapi32.lib kernel32.lib user32.lib advapi32.lib userenv.lib ws2_32.lib" + + case "x`echo \${VisualStudioVersion}`" in + x1[[4-9]]*) + LIBS="$LIBS ucrt.lib" + ;; + *) + ;; + esac + + if test "$do64bit" != "no" ; then + RC="rc" + CFLAGS_DEBUG="-nologo -Zi -Od ${runtime}d" + CFLAGS_OPTIMIZE="-nologo -O2 ${runtime}" + lflags="${lflags} -nologo -MACHINE:${MACHINE}" + LINKBIN="link" + # Avoid 'unresolved external symbol __security_cookie' errors. + # c.f. http://support.microsoft.com/?id=894573 + LIBS="$LIBS bufferoverflowU.lib" + else + RC="rc" + # -Od - no optimization + # -WX - warnings as errors + CFLAGS_DEBUG="-nologo -Z7 -Od -WX ${runtime}d" + # -O2 - create fast code (/Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) + CFLAGS_OPTIMIZE="-nologo -O2 ${runtime}" + lflags="${lflags} -nologo" + LINKBIN="link" + fi + + LIBS_GUI="gdi32.lib comdlg32.lib imm32.lib comctl32.lib shell32.lib uuid.lib winspool.lib" + + SHLIB_LD="${LINKBIN} -dll -incremental:no ${lflags}" + SHLIB_LD_LIBS='${LIBS}' + # link -lib only works when -lib is the first arg + STLIB_LD="${LINKBIN} -lib ${lflags}" + RC_OUT=-fo + RC_TYPE=-r + RC_INCLUDE=-i + RC_DEFINE=-d + RES=res + MAKE_LIB="\${STLIB_LD} -out:\[$]@" + MAKE_STUB_LIB="\${STLIB_LD} -nodefaultlib -out:\[$]@" + POST_MAKE_LIB= + MAKE_EXE="\${CC} -Fe\[$]@" + LIBPREFIX="" + + CFLAGS_DEBUG="${CFLAGS_DEBUG} -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE" + CFLAGS_OPTIMIZE="${CFLAGS_OPTIMIZE} -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE" + + EXTRA_CFLAGS="" + CFLAGS_WARNING="-W3" + LDFLAGS_DEBUG="-debug" + LDFLAGS_OPTIMIZE="-release" + + # Specify the CC output file names based on the target name + CC_OBJNAME="-Fo\[$]@" + CC_EXENAME="-Fe\"\$(shell \$(CYGPATH) '\[$]@')\"" + + # Specify linker flags depending on the type of app being + # built -- Console vs. Window. + if test "${TARGETCPU}" != "X86"; then + LDFLAGS_CONSOLE="-link ${lflags}" + LDFLAGS_WINDOW=${LDFLAGS_CONSOLE} + else + LDFLAGS_CONSOLE="-link -subsystem:console ${lflags}" + LDFLAGS_WINDOW="-link -subsystem:windows ${lflags}" + fi + fi + + if test "$do64bit" != "no" ; then + AC_DEFINE(TCL_CFG_DO64BIT) + fi + + if test "${GCC}" = "yes" ; then + AC_CACHE_CHECK(for SEH support in compiler, + tcl_cv_seh, + AC_RUN_IFELSE([AC_LANG_SOURCE([[ + #define WIN32_LEAN_AND_MEAN + #include + #undef WIN32_LEAN_AND_MEAN + + int main(int argc, char** argv) { + int a, b = 0; + __try { + a = 666 / b; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + return 0; + } + return 1; + } + ]])], + [tcl_cv_seh=yes], + [tcl_cv_seh=no], + [tcl_cv_seh=no]) + ) + if test "$tcl_cv_seh" = "no" ; then + AC_DEFINE(HAVE_NO_SEH, 1, + [Defined when mingw does not support SEH]) + fi + + # + # Check to see if the excpt.h include file provided contains the + # definition for EXCEPTION_DISPOSITION; if not, which is the case + # with Cygwin's version as of 2002-04-10, define it to be int, + # sufficient for getting the current code to work. + # + AC_CACHE_CHECK(for EXCEPTION_DISPOSITION support in include files, + tcl_cv_eh_disposition, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +# define WIN32_LEAN_AND_MEAN +# include +# undef WIN32_LEAN_AND_MEAN + ]], [[ + EXCEPTION_DISPOSITION x; + ]])], + [tcl_cv_eh_disposition=yes], + [tcl_cv_eh_disposition=no]) + ) + if test "$tcl_cv_eh_disposition" = "no" ; then + AC_DEFINE(EXCEPTION_DISPOSITION, int, + [Defined when cygwin/mingw does not support EXCEPTION DISPOSITION]) + fi + + # See if the compiler supports casting to a union type. + # This is used to stop gcc from printing a compiler + # warning when initializing a union member. + + AC_CACHE_CHECK(for cast to union support, + tcl_cv_cast_to_union, + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ + union foo { int i; double d; }; + union foo f = (union foo) (int) 0; + ]])], + [tcl_cv_cast_to_union=yes], + [tcl_cv_cast_to_union=no]) + ) + if test "$tcl_cv_cast_to_union" = "yes"; then + AC_DEFINE(HAVE_CAST_TO_UNION, 1, + [Defined when compiler supports casting to union type.]) + fi + fi + + # DL_LIBS is empty, but then we match the Unix version + AC_SUBST(DL_LIBS) + AC_SUBST(CFLAGS_DEBUG) + AC_SUBST(CFLAGS_OPTIMIZE) + AC_SUBST(CFLAGS_WARNING) + AC_SUBST(CFLAGS_NOLTO) +]) + +#------------------------------------------------------------------------ +# SC_WITH_TCL -- +# +# Location of the Tcl build directory. +# +# Arguments: +# none +# +# Results: +# +# Adds the following arguments to configure: +# --with-tcl=... +# +# Defines the following vars: +# TCL_BIN_DIR Full path to the tcl build dir. +#------------------------------------------------------------------------ + +AC_DEFUN([SC_WITH_TCL], [ + if test -d ../../tcl9.0$1/win; then + TCL_BIN_DEFAULT=../../tcl9.0$1/win + else + TCL_BIN_DEFAULT=../../tcl9.0/win + fi + + AC_ARG_WITH(tcl, [ --with-tcl=DIR use Tcl 9.x binaries from DIR], + TCL_BIN_DIR=$withval, TCL_BIN_DIR=`cd $TCL_BIN_DEFAULT; pwd`) + if test ! -d $TCL_BIN_DIR; then + AC_MSG_ERROR(Tcl directory $TCL_BIN_DIR does not exist) + fi + if test ! -f $TCL_BIN_DIR/Makefile; then + AC_MSG_ERROR(There is no Makefile in $TCL_BIN_DIR: perhaps you did not specify the Tcl *build* directory (not the toplevel Tcl directory) or you forgot to configure Tcl?) + else + echo "building against Tcl binaries in: $TCL_BIN_DIR" + fi + AC_SUBST(TCL_BIN_DIR) +]) + +#------------------------------------------------------------------------ +# SC_PROG_TCLSH +# Locate a tclsh shell installed on the system path. This macro +# will only find a Tcl shell that already exists on the system. +# It will not find a Tcl shell in the Tcl build directory or +# a Tcl shell that has been installed from the Tcl build directory. +# If a Tcl shell can't be located on the PATH, then TCLSH_PROG will +# be set to "". Extensions should take care not to create Makefile +# rules that are run by default and depend on TCLSH_PROG. An +# extension can't assume that an executable Tcl shell exists at +# build time. +# +# Arguments +# none +# +# Results +# Substitutes the following values: +# TCLSH_PROG +#------------------------------------------------------------------------ + +AC_DEFUN([SC_PROG_TCLSH], [ + AC_MSG_CHECKING([for tclsh]) + + AC_CACHE_VAL(ac_cv_path_tclsh, [ + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/tclsh[[8-9]]*.exe 2> /dev/null` \ + `ls -r $dir/tclsh* 2> /dev/null` ; do + if test x"$ac_cv_path_tclsh" = x ; then + if test -f "$j" ; then + ac_cv_path_tclsh=$j + break + fi + fi + done + done + ]) + + if test -f "$ac_cv_path_tclsh" ; then + TCLSH_PROG="$ac_cv_path_tclsh" + AC_MSG_RESULT($TCLSH_PROG) + else + # It is not an error if an installed version of Tcl can't be located. + TCLSH_PROG="" + AC_MSG_RESULT([No tclsh found on PATH]) + fi + AC_SUBST(TCLSH_PROG) +]) + +#------------------------------------------------------------------------ +# SC_BUILD_TCLSH +# Determine the fully qualified path name of the tclsh executable +# in the Tcl build directory. This macro will correctly determine +# the name of the tclsh executable even if tclsh has not yet +# been built in the build directory. The build tclsh must be used +# when running tests from an extension build directory. It is not +# correct to use the TCLSH_PROG in cases like this. +# +# Arguments +# none +# +# Results +# Substitutes the following values: +# BUILD_TCLSH +#------------------------------------------------------------------------ + +AC_DEFUN([SC_BUILD_TCLSH], [ + AC_MSG_CHECKING([for tclsh in Tcl build directory]) + BUILD_TCLSH=${TCL_BIN_DIR}/tclsh${TCL_MAJOR_VERSION}${TCL_MINOR_VERSION}\${EXESUFFIX} + AC_MSG_RESULT($BUILD_TCLSH) + AC_SUBST(BUILD_TCLSH) +]) + +#-------------------------------------------------------------------- +# SC_TCL_CFG_ENCODING TIP #59 +# +# Declare the encoding to use for embedded configuration information. +# +# Arguments: +# None. +# +# Results: +# Might append to the following vars: +# DEFS (implicit) +# +# Will define the following vars: +# TCL_CFGVAL_ENCODING +# +#-------------------------------------------------------------------- + +AC_DEFUN([SC_TCL_CFG_ENCODING], [ + AC_ARG_WITH(encoding, [ --with-encoding encoding for configuration values], with_tcencoding=${withval}) + + if test x"${with_tcencoding}" != x ; then + AC_DEFINE_UNQUOTED(TCL_CFGVAL_ENCODING,"${with_tcencoding}") + else + AC_DEFINE(TCL_CFGVAL_ENCODING,"utf-8") + fi +]) + +#-------------------------------------------------------------------- +# SC_EMBED_MANIFEST +# +# Figure out if we can embed the manifest where necessary +# +# Arguments: +# An optional manifest to merge into DLL/EXE. +# +# Results: +# Will define the following vars: +# VC_MANIFEST_EMBED_DLL +# VC_MANIFEST_EMBED_EXE +# +#-------------------------------------------------------------------- + +AC_DEFUN([SC_EMBED_MANIFEST], [ + AC_MSG_CHECKING(whether to embed manifest) + AC_ARG_ENABLE(embedded-manifest, + AS_HELP_STRING([--enable-embedded-manifest], + [embed manifest if possible (default: yes)]), + [embed_ok=$enableval], [embed_ok=yes]) + + VC_MANIFEST_EMBED_DLL= + VC_MANIFEST_EMBED_EXE= + result=no + if test "$embed_ok" = "yes" -a "${SHARED_BUILD}" = "1" \ + -a "$GCC" != "yes" ; then + # Add the magic to embed the manifest into the dll/exe + AC_EGREP_CPP([manifest needed], [ +#if defined(_MSC_VER) && _MSC_VER >= 1400 +print("manifest needed") +#endif + ], [ + # Could do a CHECK_PROG for mt, but should always be with MSVC8+ + # Could add 'if test -f' check, but manifest should be created + # in this compiler case + # Add in a manifest argument that may be specified + # XXX Needs improvement so that the test for existence accounts + # XXX for a provided (known) manifest + VC_MANIFEST_EMBED_DLL="if test -f \[$]@.manifest ; then mt.exe -nologo -manifest \[$]@.manifest $1 -outputresource:\[$]@\;2 ; fi" + VC_MANIFEST_EMBED_EXE="if test -f \[$]@.manifest ; then mt.exe -nologo -manifest \[$]@.manifest $1 -outputresource:\[$]@\;1 ; fi" + result=yes + if test "x$1" != x ; then + result="yes ($1)" + fi + ]) + fi + AC_MSG_RESULT([$result]) + AC_SUBST(VC_MANIFEST_EMBED_DLL) + AC_SUBST(VC_MANIFEST_EMBED_EXE) +]) + +#------------------------------------------------------------------------ +# SC_CC_FOR_BUILD +# For cross compiles, locate a C compiler that can generate native binaries. +# +# Arguments: +# none +# +# Results: +# Substitutes the following vars: +# CC_FOR_BUILD +# EXEEXT_FOR_BUILD +#------------------------------------------------------------------------ + +dnl Get a default for CC_FOR_BUILD to put into Makefile. +AC_DEFUN([AX_CC_FOR_BUILD], +[# Put a plausible default for CC_FOR_BUILD in Makefile. +if test -z "$CC_FOR_BUILD"; then + if test "x$cross_compiling" = "xno"; then + CC_FOR_BUILD='$(CC)' + else + AC_MSG_CHECKING([for gcc]) + AC_CACHE_VAL(ac_cv_path_cc, [ + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/gcc 2> /dev/null` \ + `ls -r $dir/gcc 2> /dev/null` ; do + if test x"$ac_cv_path_cc" = x ; then + if test -f "$j" ; then + ac_cv_path_cc=$j + break + fi + fi + done + done + ]) + fi +fi +AC_SUBST(CC_FOR_BUILD) +# Also set EXEEXT_FOR_BUILD. +if test "x$cross_compiling" = "xno"; then + EXEEXT_FOR_BUILD='$(EXEEXT)' + OBJEXT_FOR_BUILD='$(OBJEXT)' +else + OBJEXT_FOR_BUILD='.no' + AC_CACHE_CHECK([for build system executable suffix], bfd_cv_build_exeext, + [rm -f conftest* + echo 'int main () { return 0; }' > conftest.c + bfd_cv_build_exeext= + ${CC_FOR_BUILD} -o conftest conftest.c 1>&5 2>&5 + for file in conftest.*; do + case $file in + *.c | *.o | *.obj | *.ilk | *.pdb) ;; + *) bfd_cv_build_exeext=`echo $file | sed -e s/conftest//` ;; + esac + done + rm -f conftest* + test x"${bfd_cv_build_exeext}" = x && bfd_cv_build_exeext=no]) + EXEEXT_FOR_BUILD="" + test x"${bfd_cv_build_exeext}" != xno && EXEEXT_FOR_BUILD=${bfd_cv_build_exeext} +fi +AC_SUBST(EXEEXT_FOR_BUILD)])dnl +AC_SUBST(OBJEXT_FOR_BUILD)])dnl + + + +#------------------------------------------------------------------------ +# SC_ZIPFS_SUPPORT +# Locate a zip encoder installed on the system path, or none. +# +# Arguments: +# none +# +# Results: +# Substitutes the following vars: +# ZIP_PROG +# ZIP_PROG_OPTIONS +# ZIP_PROG_VFSSEARCH +# ZIP_INSTALL_OBJS +#------------------------------------------------------------------------ + +AC_DEFUN([SC_ZIPFS_SUPPORT], [ + ZIP_PROG="" + ZIP_PROG_OPTIONS="" + ZIP_PROG_VFSSEARCH="" + ZIP_INSTALL_OBJS="" + + AC_MSG_CHECKING([for zip]) + AC_CACHE_VAL(ac_cv_path_zip, [ + search_path=`echo ${PATH} | sed -e 's/:/ /g'` + for dir in $search_path ; do + for j in `ls -r $dir/zip 2> /dev/null` \ + `ls -r $dir/zip 2> /dev/null` ; do + if test x"$ac_cv_path_zip" = x ; then + if test -f "$j" ; then + ac_cv_path_zip=$j + break + fi + fi + done + done + ]) + if test -f "$ac_cv_path_zip" ; then + ZIP_PROG="$ac_cv_path_zip" + AC_MSG_RESULT([$ZIP_PROG]) + ZIP_PROG_OPTIONS="-rq" + ZIP_PROG_VFSSEARCH="*" + AC_MSG_RESULT([Found INFO Zip in environment]) + # Use standard arguments for zip + else + # It is not an error if an installed version of Zip can't be located. + # We can use the locally distributed minizip instead + ZIP_PROG="./minizip${EXEEXT_FOR_BUILD}" + ZIP_PROG_OPTIONS="-o -r" + ZIP_PROG_VFSSEARCH="*" + ZIP_INSTALL_OBJS="minizip${EXEEXT_FOR_BUILD}" + AC_MSG_RESULT([No zip found on PATH building minizip]) + fi + AC_SUBST(ZIP_PROG) + AC_SUBST(ZIP_PROG_OPTIONS) + AC_SUBST(ZIP_PROG_VFSSEARCH) + AC_SUBST(ZIP_INSTALL_OBJS) +]) diff --git a/vendor/tcl/win/tcl.rc b/vendor/tcl/win/tcl.rc new file mode 100644 index 00000000..ba0f83fe --- /dev/null +++ b/vendor/tcl/win/tcl.rc @@ -0,0 +1,51 @@ +// +// Version Resource Script +// + +#include +#include + +// +// 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 diff --git a/vendor/tcl/win/tclAppInit.c b/vendor/tcl/win/tclAppInit.c new file mode 100644 index 00000000..c4a44008 --- /dev/null +++ b/vendor/tcl/win/tclAppInit.c @@ -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 +#undef STRICT +#undef WIN32_LEAN_AND_MEAN +#include +#include +#include +#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: + */ diff --git a/vendor/tcl/win/tclConfig.sh.in b/vendor/tcl/win/tclConfig.sh.in new file mode 100644 index 00000000..c980af61 --- /dev/null +++ b/vendor/tcl/win/tclConfig.sh.in @@ -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@' diff --git a/vendor/tcl/win/tclUuid.h.in b/vendor/tcl/win/tclUuid.h.in new file mode 100644 index 00000000..cbb83e44 --- /dev/null +++ b/vendor/tcl/win/tclUuid.h.in @@ -0,0 +1 @@ +#define TCL_VERSION_UUID \ diff --git a/vendor/tcl/win/tclWin32Dll.c b/vendor/tcl/win/tclWin32Dll.c new file mode 100644 index 00000000..8d83130d --- /dev/null +++ b/vendor/tcl/win/tclWin32Dll.c @@ -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 +#elif defined(_MSC_VER) +# include +#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: + */ diff --git a/vendor/tcl/win/tclWinChan.c b/vendor/tcl/win/tclWinChan.c new file mode 100644 index 00000000..393b48c5 --- /dev/null +++ b/vendor/tcl/win/tclWinChan.c @@ -0,0 +1,1760 @@ +/* + * tclWinChan.c + * + * Channel drivers for Windows channels based on files, command pipes and + * TCP sockets. + * + * 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" +#include "tclFileSystem.h" +#include "tclIO.h" + +/* + * State flags used in the info structures below. + */ + +#define FILE_PENDING (1<<0) /* Message is pending in the queue. */ +#define FILE_ASYNC (1<<1) /* Channel is non-blocking. */ +#define FILE_APPEND (1<<2) /* File is in append mode. */ + +#define FILE_TYPE_SERIAL (FILE_TYPE_PIPE+1) +#define FILE_TYPE_CONSOLE (FILE_TYPE_PIPE+2) + +/* + * The following structure contains per-instance data for a file based + * channel. + */ + +typedef struct FileInfo { + Tcl_Channel channel; /* Pointer to channel structure. */ + int validMask; /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, or TCL_EXCEPTION: indicates + * which operations are valid on the file. */ + int watchMask; /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, or TCL_EXCEPTION: indicates + * which events should be reported. */ + int flags; /* State flags, see above for a list. */ + HANDLE handle; /* Input/output file. */ + struct FileInfo *nextPtr; /* Pointer to next registered file. */ + int dirty; /* Boolean flag. Set if the OS may have data + * pending on the channel. */ +} FileInfo; + +typedef struct { + /* + * List of all file channels currently open. + */ + + FileInfo *firstFilePtr; +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +/* + * The following structure is what is added to the Tcl event queue when file + * events are generated. + */ + +typedef struct { + Tcl_Event header; /* Information that is standard for all + * events. */ + FileInfo *infoPtr; /* Pointer to file info structure. Note that + * we still have to verify that the file + * exists before dereferencing this + * pointer. */ +} FileEvent; + +/* + * Static routines for this file: + */ + +static int FileBlockProc(void *instanceData, int mode); +static void FileChannelExitHandler(void *clientData); +static void FileCheckProc(void *clientData, int flags); +static int FileCloseProc(void *instanceData, + Tcl_Interp *interp, int flags); +static int FileEventProc(Tcl_Event *evPtr, int flags); +static int FileGetHandleProc(void *instanceData, + int direction, void **handlePtr); +static int FileGetOptionProc(void *instanceData, + Tcl_Interp *interp, const char *optionName, + Tcl_DString *dsPtr); +static ThreadSpecificData *FileInit(void); +static int FileInputProc(void *instanceData, char *buf, + int toRead, int *errorCode); +static int FileOutputProc(void *instanceData, + const char *buf, int toWrite, int *errorCode); +static long long FileWideSeekProc(void *instanceData, + long long offset, int mode, int *errorCode); +static void FileSetupProc(void *clientData, int flags); +static void FileWatchProc(void *instanceData, int mask); +static void FileThreadActionProc(void *instanceData, + int action); +static int FileTruncateProc(void *instanceData, + long long length); +static DWORD FileGetType(HANDLE handle); +static int NativeIsComPort(const WCHAR *nativeName); +static Tcl_Channel OpenFileChannel(HANDLE handle, char *channelName, + int permissions, int appendMode); + +/* + * This structure describes the channel type structure for file based IO. + */ + +static const Tcl_ChannelType fileChannelType = { + "file", + TCL_CHANNEL_VERSION_5, + NULL, /* Deprecated. */ + FileInputProc, + FileOutputProc, + NULL, /* Deprecated. */ + NULL, /* Set option proc. */ + FileGetOptionProc, + FileWatchProc, + FileGetHandleProc, + FileCloseProc, + FileBlockProc, + NULL, /* Flush proc. */ + NULL, /* Bubbled event handler proc. */ + FileWideSeekProc, + FileThreadActionProc, + FileTruncateProc +}; + +/* + * General useful clarification macros. + */ + +#define SET_FLAG(var, flag) ((var) |= (flag)) +#define CLEAR_FLAG(var, flag) ((var) &= ~(flag)) +#define TEST_FLAG(value, flag) (((value) & (flag)) != 0) + +/* + * The number of 100-ns intervals between the Windows system epoch (1601-01-01 + * on the proleptic Gregorian calendar) and the Posix epoch (1970-01-01). + */ + +#define POSIX_EPOCH_AS_FILETIME \ + ((long long) 116444736 * (long long) 1000000000) + +/* + *---------------------------------------------------------------------- + * + * TclWinGenerateChannelName -- + * + * This function generates names for channels. + * + * Results: + * None. + * + * Side effects: + * Creates a new window and creates an exit handler. + * + *---------------------------------------------------------------------- + */ +void +TclWinGenerateChannelName( + char channelName[], /* Buffer to accept the name. */ + const char *channelTypeName,/* Name of type of channel. */ + void *channelImpl) /* Pointer to channel implementation + * structure, used to generate a unique + * ID. */ +{ + snprintf(channelName, 16 + TCL_INTEGER_SPACE, "%s%" TCL_Z_MODIFIER "x", + channelTypeName, (size_t) channelImpl); +} + +/* + *---------------------------------------------------------------------- + * + * FileInit -- + * + * This function creates the window used to simulate file events. + * + * Results: + * None. + * + * Side effects: + * Creates a new window and creates an exit handler. + * + *---------------------------------------------------------------------- + */ + +static ThreadSpecificData * +FileInit(void) +{ + ThreadSpecificData *tsdPtr = + (ThreadSpecificData *) TclThreadDataKeyGet(&dataKey); + + if (tsdPtr == NULL) { + tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->firstFilePtr = NULL; + Tcl_CreateEventSource(FileSetupProc, FileCheckProc, NULL); + Tcl_CreateThreadExitHandler(FileChannelExitHandler, NULL); + } + return tsdPtr; +} + +/* + *---------------------------------------------------------------------- + * + * FileChannelExitHandler -- + * + * This function is called to cleanup the channel driver before Tcl is + * unloaded. + * + * Results: + * None. + * + * Side effects: + * Destroys the communication window. + * + *---------------------------------------------------------------------- + */ + +static void +FileChannelExitHandler( + TCL_UNUSED(void *)) +{ + Tcl_DeleteEventSource(FileSetupProc, FileCheckProc, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * FileSetupProc -- + * + * This function is invoked before Tcl_DoOneEvent blocks waiting for an + * event. + * + * Results: + * None. + * + * Side effects: + * Adjusts the block time if needed. + * + *---------------------------------------------------------------------- + */ + +void +FileSetupProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + FileInfo *infoPtr; + Tcl_Time blockTime = { 0, 0 }; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!TEST_FLAG(flags, TCL_FILE_EVENTS)) { + return; + } + + /* + * Check to see if there is a ready file. If so, poll. + */ + + for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->watchMask) { + Tcl_SetMaxBlockTime(&blockTime); + break; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FileCheckProc -- + * + * This function is called by Tcl_DoOneEvent to check the file event + * source for events. + * + * Results: + * None. + * + * Side effects: + * May queue an event. + * + *---------------------------------------------------------------------- + */ + +static void +FileCheckProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + FileEvent *evPtr; + FileInfo *infoPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!TEST_FLAG(flags, TCL_FILE_EVENTS)) { + return; + } + + /* + * Queue events for any ready files that don't already have events queued + * (caused by persistent states that won't generate WinSock events). + */ + + for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->watchMask && !TEST_FLAG(infoPtr->flags, FILE_PENDING)) { + SET_FLAG(infoPtr->flags, FILE_PENDING); + evPtr = (FileEvent *)Tcl_Alloc(sizeof(FileEvent)); + evPtr->header.proc = FileEventProc; + evPtr->infoPtr = infoPtr; + Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FileEventProc -- + * + * This function is invoked by Tcl_ServiceEvent when a file event reaches + * the front of the event queue. This function invokes Tcl_NotifyChannel + * on the file. + * + * Results: + * Returns 1 if the event was handled, meaning it should be removed from + * the queue. Returns 0 if the event was not handled, meaning it should + * stay on the queue. The only time the event isn't handled is if the + * TCL_FILE_EVENTS flag bit isn't set. + * + * Side effects: + * Whatever the notifier callback does. + * + *---------------------------------------------------------------------- + */ + +static int +FileEventProc( + Tcl_Event *evPtr, /* Event to service. */ + int flags) /* Flags that indicate what events to handle, + * such as TCL_FILE_EVENTS. */ +{ + FileEvent *fileEvPtr = (FileEvent *)evPtr; + FileInfo *infoPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!TEST_FLAG(flags, TCL_FILE_EVENTS)) { + return 0; + } + + /* + * Search through the list of watched files for the one whose handle + * matches the event. We do this rather than simply dereferencing the + * handle in the event so that files can be deleted while the event is in + * the queue. + */ + + for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (fileEvPtr->infoPtr == infoPtr) { + CLEAR_FLAG(infoPtr->flags, FILE_PENDING); + Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask); + break; + } + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * FileBlockProc -- + * + * Set blocking or non-blocking mode on channel. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or non-blocking mode. + * + *---------------------------------------------------------------------- + */ + +static int +FileBlockProc( + void *instanceData, /* Instance data for channel. */ + int mode) /* TCL_MODE_BLOCKING or + * TCL_MODE_NONBLOCKING. */ +{ + FileInfo *infoPtr = (FileInfo *)instanceData; + + /* + * Files on Windows can not be switched between blocking and nonblocking, + * hence we have to emulate the behavior. This is done in the input + * function by checking against a bit in the state. We set or unset the + * bit here to cause the input function to emulate the correct behavior. + */ + + if (mode == TCL_MODE_NONBLOCKING) { + SET_FLAG(infoPtr->flags, FILE_ASYNC); + } else { + CLEAR_FLAG(infoPtr->flags, FILE_ASYNC); + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FileCloseProc -- + * + * Closes the IO channel. + * + * Results: + * 0 if successful, the value of errno if failed. + * + * Side effects: + * Closes the physical channel + * + *---------------------------------------------------------------------- + */ + +static int +FileCloseProc( + void *instanceData, /* Pointer to FileInfo structure. */ + TCL_UNUSED(Tcl_Interp *), + int flags) +{ + FileInfo *fileInfoPtr = (FileInfo *)instanceData; + FileInfo *infoPtr; + ThreadSpecificData *tsdPtr; + int errorCode = 0; + + if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { + return EINVAL; + } + + /* + * Remove the file from the watch list. + */ + + FileWatchProc(instanceData, 0); + + /* + * Don't close the Win32 handle if the handle is a standard channel during + * the thread exit process. Otherwise, one thread may kill the stdio of + * another. + */ + + if (!TclInThreadExit() + || ((GetStdHandle(STD_INPUT_HANDLE) != fileInfoPtr->handle) + && (GetStdHandle(STD_OUTPUT_HANDLE) != fileInfoPtr->handle) + && (GetStdHandle(STD_ERROR_HANDLE) != fileInfoPtr->handle))) { + if (CloseHandle(fileInfoPtr->handle) == FALSE) { + Tcl_WinConvertError(GetLastError()); + errorCode = errno; + } + } + + /* + * See if this FileInfo* is still on the thread local list. + */ + + tsdPtr = TCL_TSD_INIT(&dataKey); + for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr == fileInfoPtr) { + /* + * This channel exists on the thread local list. It should have + * been removed by an earlier Threadaction call, but do that now + * since just deallocating fileInfoPtr would leave an deallocated + * pointer on the thread local list. + */ + + FileThreadActionProc(fileInfoPtr,TCL_CHANNEL_THREAD_REMOVE); + break; + } + } + Tcl_Free(fileInfoPtr); + return errorCode; +} + +/* + *---------------------------------------------------------------------- + * + * FileWideSeekProc -- + * + * Seeks on a file-based channel. Returns the new position. + * + * Results: + * -1 if failed, the new position if successful. If failed, it also sets + * *errorCodePtr to the error code. + * + * Side effects: + * Moves the location at which the channel will be accessed in future + * operations. + * + *---------------------------------------------------------------------- + */ + +static long long +FileWideSeekProc( + void *instanceData, /* File state. */ + long long offset, /* Offset to seek to. */ + int mode, /* Relative to where should we seek? */ + int *errorCodePtr) /* To store error code. */ +{ + FileInfo *infoPtr = (FileInfo *)instanceData; + DWORD moveMethod; + LONG newPos, newPosHigh; + + *errorCodePtr = 0; + if (mode == SEEK_SET) { + moveMethod = FILE_BEGIN; + } else if (mode == SEEK_CUR) { + moveMethod = FILE_CURRENT; + } else { + moveMethod = FILE_END; + } + + newPosHigh = (LONG)(offset >> 32); + newPos = SetFilePointer(infoPtr->handle, (LONG)offset, + &newPosHigh, moveMethod); + if (newPos == (LONG) INVALID_SET_FILE_POINTER) { + DWORD winError = GetLastError(); + + if (winError != NO_ERROR) { + Tcl_WinConvertError(winError); + *errorCodePtr = errno; + return -1; + } + } + return (((long long)((unsigned)newPos)) + | ((long long)newPosHigh << 32)); +} + +/* + *---------------------------------------------------------------------- + * + * FileTruncateProc -- + * + * Truncates a file-based channel. Returns the error code. + * + * Results: + * 0 if successful, POSIX-y error code if it failed. + * + * Side effects: + * Truncates the file, may move file pointers too. + * + *---------------------------------------------------------------------- + */ + +static int +FileTruncateProc( + void *instanceData, /* File state. */ + long long length) /* Length to truncate at. */ +{ + FileInfo *infoPtr = (FileInfo *)instanceData; + LONG newPos, newPosHigh, oldPos, oldPosHigh; + + /* + * Save where we were... + */ + + oldPosHigh = 0; + oldPos = SetFilePointer(infoPtr->handle, 0, &oldPosHigh, FILE_CURRENT); + if (oldPos == (LONG) INVALID_SET_FILE_POINTER) { + DWORD winError = GetLastError(); + + if (winError != NO_ERROR) { + Tcl_WinConvertError(winError); + return errno; + } + } + + /* + * Move to where we want to truncate + */ + + newPosHigh = (LONG)(length >> 32); + newPos = SetFilePointer(infoPtr->handle, (LONG)length, + &newPosHigh, FILE_BEGIN); + if (newPos == (LONG) INVALID_SET_FILE_POINTER) { + DWORD winError = GetLastError(); + + if (winError != NO_ERROR) { + Tcl_WinConvertError(winError); + return errno; + } + } + + /* + * Perform the truncation (unlike POSIX ftruncate(), we needed to move to + * the location to truncate at first). + */ + + if (!SetEndOfFile(infoPtr->handle)) { + Tcl_WinConvertError(GetLastError()); + return errno; + } + + /* + * Move back. If this last step fails, we don't care; it's just a "best + * effort" attempt to restore our file pointer to where it was. + */ + + SetFilePointer(infoPtr->handle, oldPos, &oldPosHigh, FILE_BEGIN); + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FileInputProc -- + * + * Reads input from the IO channel into the buffer given. Returns count + * of how many bytes were actually read, and an error indication. + * + * Results: + * A count of how many bytes were read is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Reads input from the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +FileInputProc( + void *instanceData, /* File state. */ + char *buf, /* Where to store data read. */ + int bufSize, /* Num bytes available in buffer. */ + int *errorCode) /* Where to store error code. */ +{ + FileInfo *infoPtr = (FileInfo *)instanceData; + DWORD bytesRead; + + *errorCode = 0; + + /* + * TODO: This comment appears to be out of date. We *do* have a console + * driver, over in tclWinConsole.c. After some Windows developer confirms, + * this comment should be revised. + * + * Note that we will block on reads from a console buffer until a full + * line has been entered. The only way I know of to get around this is to + * write a console driver. We should probably do this at some point, but + * for now, we just block. The same problem exists for files being read + * over the network. + */ + + if (ReadFile(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &bytesRead, + (LPOVERLAPPED) NULL) != FALSE) { + return (int)bytesRead; + } + + Tcl_WinConvertError(GetLastError()); + *errorCode = errno; + if (errno == EPIPE) { + return 0; + } + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * FileOutputProc -- + * + * Writes the given output on the IO channel. Returns count of how many + * characters were actually written, and an error indication. + * + * Results: + * A count of how many characters were written is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Writes output on the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +FileOutputProc( + void *instanceData, /* File state. */ + const char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCode) /* Where to store error code. */ +{ + FileInfo *infoPtr = (FileInfo *)instanceData; + DWORD bytesWritten; + + *errorCode = 0; + + /* + * If we are writing to a file that was opened with O_APPEND, we need to + * seek to the end of the file before writing the current buffer. + */ + + if (TEST_FLAG(infoPtr->flags, FILE_APPEND)) { + SetFilePointer(infoPtr->handle, 0, NULL, FILE_END); + } + + if (WriteFile(infoPtr->handle, (LPVOID) buf, (DWORD) toWrite, + &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) { + Tcl_WinConvertError(GetLastError()); + *errorCode = errno; + return -1; + } + infoPtr->dirty = 1; + return (int)bytesWritten; +} + +/* + *---------------------------------------------------------------------- + * + * FileWatchProc -- + * + * Called by the notifier to set up to watch for events on this channel. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +FileWatchProc( + void *instanceData, /* File state. */ + int mask) /* What events to watch for; OR-ed combination + * of TCL_READABLE, TCL_WRITABLE and + * TCL_EXCEPTION. */ +{ + FileInfo *infoPtr = (FileInfo *)instanceData; + Tcl_Time blockTime = { 0, 0 }; + + /* + * Since the file is always ready for events, we set the block time to + * zero so we will poll. + */ + + infoPtr->watchMask = mask & infoPtr->validMask; + if (infoPtr->watchMask) { + Tcl_SetMaxBlockTime(&blockTime); + } +} + +/* + *---------------------------------------------------------------------- + * + * FileGetHandleProc -- + * + * Called from Tcl_GetChannelHandle to retrieve OS handles from a file + * based channel. + * + * Results: + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no + * handle for the specified direction. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +FileGetHandleProc( + void *instanceData, /* The file state. */ + int direction, /* TCL_READABLE or TCL_WRITABLE */ + void **handlePtr) /* Where to store the handle. */ +{ + FileInfo *infoPtr = (FileInfo *)instanceData; + + if (!TEST_FLAG(direction, infoPtr->validMask)) { + return TCL_ERROR; + } + + *handlePtr = (void *)infoPtr->handle; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * FileGetOptionProc -- + * + * Gets an option associated with an open file. If the optionName arg is + * non-NULL, retrieves the value of that option. If the optionName arg is + * NULL, retrieves a list of alternating option names and values for the + * given channel. + * + * Results: + * A standard Tcl result. Also sets the supplied DString to the string + * value of the option(s) returned. Sets error message if needed + * (by calling Tcl_BadChannelOption). + * + *---------------------------------------------------------------------- + */ + +static inline ULONGLONG +CombineDwords( + DWORD hi, + DWORD lo) +{ + ULARGE_INTEGER converter; + + converter.LowPart = lo; + converter.HighPart = hi; + return converter.QuadPart; +} + +static inline time_t +ToCTime( + FILETIME fileTime) /* UTC time */ +{ + LARGE_INTEGER convertedTime; + + convertedTime.LowPart = fileTime.dwLowDateTime; + convertedTime.HighPart = (LONG) fileTime.dwHighDateTime; + + return (time_t) ((convertedTime.QuadPart - + (long long) POSIX_EPOCH_AS_FILETIME) / (long long) 10000000); +} + +static Tcl_Obj * +StatOpenFile( + FileInfo *infoPtr) +{ + DWORD attr; + int dev, nlink = 1; + unsigned short mode; + unsigned long long size, inode; + long long atime, ctime, mtime; + BY_HANDLE_FILE_INFORMATION data; + Tcl_Obj *dictObj; + + if (GetFileInformationByHandle(infoPtr->handle, &data) != TRUE) { + Tcl_SetErrno(ENOENT); + return NULL; + } + + atime = ToCTime(data.ftLastAccessTime); + mtime = ToCTime(data.ftLastWriteTime); + ctime = ToCTime(data.ftCreationTime); + attr = data.dwFileAttributes; + size = CombineDwords(data.nFileSizeHigh, data.nFileSizeLow); + nlink = data.nNumberOfLinks; + + /* + * Unfortunately our stat definition's inode field (unsigned short) will + * throw away most of the precision we have here, which means we can't + * rely on inode as a unique identifier of a file. We'd really like to do + * something like how we handle 'st_size'. + */ + + inode = CombineDwords(data.nFileIndexHigh, data.nFileIndexLow); + + dev = data.dwVolumeSerialNumber; + + /* + * Note that this code has no idea whether the file can be executed. + */ + + mode = (attr & FILE_ATTRIBUTE_DIRECTORY) ? S_IFDIR|S_IEXEC : S_IFREG; + mode |= (attr & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD|S_IWRITE; + mode |= (mode & (S_IREAD|S_IWRITE|S_IEXEC)) >> 3; + mode |= (mode & (S_IREAD|S_IWRITE|S_IEXEC)) >> 6; + + /* + * We don't construct a Tcl_StatBuf; we're using the info immediately. + */ + + TclNewObj(dictObj); +#define STORE_ELEM(name, value) TclDictPut(NULL, dictObj, name, value) + + STORE_ELEM("dev", Tcl_NewWideIntObj((long) dev)); + STORE_ELEM("ino", Tcl_NewWideIntObj((long long) inode)); + STORE_ELEM("nlink", Tcl_NewIntObj(nlink)); + STORE_ELEM("uid", Tcl_NewIntObj(0)); + STORE_ELEM("gid", Tcl_NewIntObj(0)); + STORE_ELEM("size", Tcl_NewWideIntObj((long long) size)); + STORE_ELEM("atime", Tcl_NewWideIntObj(atime)); + STORE_ELEM("mtime", Tcl_NewWideIntObj(mtime)); + STORE_ELEM("ctime", Tcl_NewWideIntObj(ctime)); + STORE_ELEM("mode", Tcl_NewWideIntObj(mode)); + + /* + * Windows only has files and directories, as far as we're concerned. + * Anything else and we definitely couldn't have got here anyway. + */ + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + STORE_ELEM("type", Tcl_NewStringObj("directory", TCL_INDEX_NONE)); + } else { + STORE_ELEM("type", Tcl_NewStringObj("file", TCL_INDEX_NONE)); + } +#undef STORE_ELEM + + return dictObj; +} + +static int +FileGetOptionProc( + void *instanceData, /* The file state. */ + Tcl_Interp *interp, /* For error reporting. */ + const char *optionName, /* What option to read, or NULL for all. */ + Tcl_DString *dsPtr) /* Where to write the value read. */ +{ + FileInfo *infoPtr = (FileInfo *)instanceData; + int valid = 0; /* Flag if valid option parsed. */ + int len; + + if (optionName == NULL) { + len = 0; + valid = 1; + } else { + len = strlen(optionName); + } + + /* + * Get option -stat + * Option is readonly and returned by [fconfigure chan -stat] but not + * returned by [fconfigure chan] without explicit option name. + */ + + if ((len > 1) && (strncmp(optionName, "-stat", len) == 0)) { + Tcl_Obj *dictObj = StatOpenFile(infoPtr); + const char *dictContents; + Tcl_Size dictLength; + + if (dictObj == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't read file channel status: %s", + Tcl_PosixError(interp))); + return TCL_ERROR; + } + + /* + * Transfer dictionary to the DString. Note that we don't do this as + * an element as this is an option that can't be retrieved with a + * general probe. + */ + + dictContents = TclGetStringFromObj(dictObj, &dictLength); + Tcl_DStringAppend(dsPtr, dictContents, dictLength); + Tcl_DecrRefCount(dictObj); + return TCL_OK; + } + + if (valid) { + return TCL_OK; + } + return Tcl_BadChannelOption(interp, optionName, + "stat"); +} + +/* + *---------------------------------------------------------------------- + * + * TclpOpenFileChannel -- + * + * Open an File based channel on Unix systems. + * + * Results: + * The new channel or NULL. If NULL, the output argument errorCodePtr is + * set to a POSIX error. + * + * Side effects: + * May open the channel and may cause creation of a file on the file + * system. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +TclpOpenFileChannel( + Tcl_Interp *interp, /* Interpreter for error reporting; can be + * NULL. */ + Tcl_Obj *pathPtr, /* Name of file to open. */ + int mode, /* POSIX mode. */ + int permissions) /* If the open involves creating a file, with + * what modes to create it? */ +{ + Tcl_Channel channel = 0; + int channelPermissions = 0; + DWORD accessMode = 0, createMode, shareMode, flags; + const WCHAR *nativeName; + HANDLE handle; + char channelName[16 + TCL_INTEGER_SPACE]; + TclFile readFile = NULL, writeFile = NULL; + + nativeName = (const WCHAR *)Tcl_FSGetNativePath(pathPtr); + if (nativeName == NULL) { + if (interp) { + /* + * We need this just to ensure we return the correct error messages under + * some circumstances (relative paths only), so because the normalization + * is very expensive, don't invoke it for native or absolute paths. + * Note: since paths starting with ~ are relative in 9.0 for windows, + * it doesn't need to consider tilde expansion (in opposite to 8.x). + */ + if ( + ( + !TclFSCwdIsNative() && + (Tcl_FSGetPathType(pathPtr) != TCL_PATH_ABSOLUTE) + ) && + Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL + ) { + return NULL; + } + + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't open \"%s\": filename is invalid on this platform", + TclGetString(pathPtr))); + } + return NULL; + } + + switch (mode & O_ACCMODE) { + case O_RDONLY: + accessMode = GENERIC_READ; + channelPermissions = TCL_READABLE; + break; + case O_WRONLY: + accessMode = GENERIC_WRITE; + channelPermissions = TCL_WRITABLE; + break; + case O_RDWR: + accessMode = (GENERIC_READ | GENERIC_WRITE); + channelPermissions = (TCL_READABLE | TCL_WRITABLE); + break; + default: + Tcl_Panic("TclpOpenFileChannel: invalid mode value"); + break; + } + + /* + * Map the creation flags to the NT create mode. + */ + + switch (mode & (O_CREAT | O_EXCL | O_TRUNC)) { + case (O_CREAT | O_EXCL): + case (O_CREAT | O_EXCL | O_TRUNC): + createMode = CREATE_NEW; + break; + case (O_CREAT | O_TRUNC): + createMode = CREATE_ALWAYS; + break; + case O_CREAT: + createMode = OPEN_ALWAYS; + break; + case O_TRUNC: + case (O_TRUNC | O_EXCL): + createMode = TRUNCATE_EXISTING; + break; + default: + createMode = OPEN_EXISTING; + break; + } + + /* + * [2413550] Avoid double-open of serial ports on Windows. Special + * handling for Windows serial ports by a "name-hint" to directly open it + * with the OVERLAPPED flag set. + */ + + if (NativeIsComPort(nativeName)) { + handle = TclWinSerialOpen(INVALID_HANDLE_VALUE, nativeName, accessMode); + if (handle == INVALID_HANDLE_VALUE) { + Tcl_WinConvertError(GetLastError()); + if (interp) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't open serial \"%s\": %s", + TclGetString(pathPtr), Tcl_PosixError(interp))); + } + return NULL; + } + + /* + * For natively-named Windows serial ports we are done. + */ + + channel = TclWinOpenSerialChannel(handle, channelName, + channelPermissions); + + return channel; + } + + /* + * If the file is being created, get the file attributes from the + * permissions argument, else use the existing file attributes. + */ + + if (TEST_FLAG(mode, O_CREAT)) { + if (TEST_FLAG(permissions, S_IWRITE)) { + flags = FILE_ATTRIBUTE_NORMAL; + } else { + flags = FILE_ATTRIBUTE_READONLY; + } + } else { + flags = GetFileAttributesW(nativeName); + if (flags == 0xFFFFFFFF) { + flags = 0; + } + } + + /* + * Set up the file sharing mode. We want to allow simultaneous access. + */ + + shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + /* + * Now we get to create the file. + */ + + handle = CreateFileW(nativeName, accessMode, shareMode, + NULL, createMode, flags, (HANDLE) NULL); + + if (handle == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + + if ((err & 0xFFFFL) == ERROR_OPEN_FAILED) { + err = TEST_FLAG(mode, O_CREAT) ? ERROR_FILE_EXISTS + : ERROR_FILE_NOT_FOUND; + } + Tcl_WinConvertError(err); + if (interp) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't open \"%s\": %s", + TclGetString(pathPtr), Tcl_PosixError(interp))); + } + return NULL; + } + + channel = NULL; + + switch (FileGetType(handle)) { + case FILE_TYPE_SERIAL: + /* + * Natively named serial ports "com1-9", "\\\\.\\comXX" are already + * done with the code above. Here we handle all other serial port + * names. + * + * Reopen channel for OVERLAPPED operation. Normally this shouldn't + * fail, because the channel exists. + */ + + handle = TclWinSerialOpen(handle, nativeName, accessMode); + if (handle == INVALID_HANDLE_VALUE) { + Tcl_WinConvertError(GetLastError()); + if (interp) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't reopen serial \"%s\": %s", + TclGetString(pathPtr), Tcl_PosixError(interp))); + } + return NULL; + } + channel = TclWinOpenSerialChannel(handle, channelName, + channelPermissions); + break; + case FILE_TYPE_CONSOLE: + channel = TclWinOpenConsoleChannel(handle, channelName, + channelPermissions); + break; + case FILE_TYPE_PIPE: + if (TEST_FLAG(channelPermissions, TCL_READABLE)) { + readFile = TclWinMakeFile(handle); + } + if (TEST_FLAG(channelPermissions, TCL_WRITABLE)) { + writeFile = TclWinMakeFile(handle); + } + channel = TclpCreateCommandChannel(readFile, writeFile, NULL, 0, NULL); + break; + case FILE_TYPE_CHAR: + case FILE_TYPE_DISK: + case FILE_TYPE_UNKNOWN: + channel = OpenFileChannel(handle, channelName, + channelPermissions, + TEST_FLAG(mode, O_APPEND) ? FILE_APPEND : 0); + break; + + default: + /* + * The handle is of an unknown type, probably /dev/nul equivalent or + * possibly a closed handle. + */ + + channel = NULL; + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't open \"%s\": bad file type", + TclGetString(pathPtr))); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "CHANNEL", "BAD_TYPE", + (char *)NULL); + break; + } + + return channel; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_MakeFileChannel -- + * + * Creates a Tcl_Channel from an existing platform specific file handle. + * + * Results: + * The Tcl_Channel created around the preexisting file. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +Tcl_MakeFileChannel( + void *rawHandle, /* OS level handle */ + int mode) /* OR'ed combination of TCL_READABLE and + * TCL_WRITABLE to indicate file mode. */ +{ +#if defined(HAVE_NO_SEH) && !defined(_WIN64) && !defined(__clang__) + TCLEXCEPTION_REGISTRATION registration; +#endif + char channelName[16 + TCL_INTEGER_SPACE]; + Tcl_Channel channel = NULL; + HANDLE handle = (HANDLE) rawHandle; + HANDLE dupedHandle; + TclFile readFile = NULL, writeFile = NULL; + BOOL result; + + if ((mode & (TCL_READABLE|TCL_WRITABLE)) == 0) { + return NULL; + } + + switch (FileGetType(handle)) { + case FILE_TYPE_SERIAL: + channel = TclWinOpenSerialChannel(handle, channelName, mode); + break; + case FILE_TYPE_CONSOLE: + channel = TclWinOpenConsoleChannel(handle, channelName, mode); + break; + case FILE_TYPE_PIPE: + if (TEST_FLAG(mode, TCL_READABLE)) { + readFile = TclWinMakeFile(handle); + } + if (TEST_FLAG(mode, TCL_WRITABLE)) { + writeFile = TclWinMakeFile(handle); + } + channel = TclpCreateCommandChannel(readFile, writeFile, NULL, 0, NULL); + break; + + case FILE_TYPE_DISK: + case FILE_TYPE_CHAR: + channel = OpenFileChannel(handle, channelName, mode, 0); + break; + + case FILE_TYPE_UNKNOWN: + default: + /* + * The handle is of an unknown type. Test the validity of this OS + * handle by duplicating it, then closing the dupe. The Win32 API + * doesn't provide an IsValidHandle() function, so we have to emulate + * it here. This test will not work on a console handle reliably, + * which is why we can't test every handle that comes into this + * function in this way. + */ + + result = DuplicateHandle(GetCurrentProcess(), handle, + GetCurrentProcess(), &dupedHandle, 0, FALSE, + DUPLICATE_SAME_ACCESS); + + if (result == 0) { + /* + * Unable to make a duplicate. It's definitely invalid at this + * point. + */ + + return NULL; + } + + /* + * Use structured exception handling (Win32 SEH) to protect the close + * of this duped handle which might throw EXCEPTION_INVALID_HANDLE. + */ + + result = 0; +#if defined(HAVE_NO_SEH) && !defined(_WIN64) && !defined(__clang__) + /* + * Don't have SEH available, do things the hard way. Note that this + * needs to be one block of asm, to avoid stack imbalance; also, it is + * illegal for one asm block to contain a jump to another. + */ + + __asm__ __volatile__ ( + + /* + * Pick up parameters before messing with the stack + */ + + "movl %[dupedHandle], %%ebx" "\n\t" + + /* + * Construct an TCLEXCEPTION_REGISTRATION to protect the call to + * CloseHandle. + */ + + "leal %[registration], %%edx" "\n\t" + "movl %%fs:0, %%eax" "\n\t" + "movl %%eax, 0x0(%%edx)" "\n\t" /* link */ + "leal 1f, %%eax" "\n\t" + "movl %%eax, 0x4(%%edx)" "\n\t" /* handler */ + "movl %%ebp, 0x8(%%edx)" "\n\t" /* ebp */ + "movl %%esp, 0xC(%%edx)" "\n\t" /* esp */ + "movl $0, 0x10(%%edx)" "\n\t" /* status */ + + /* + * Link the TCLEXCEPTION_REGISTRATION on the chain. + */ + + "movl %%edx, %%fs:0" "\n\t" + + /* + * Call CloseHandle(dupedHandle). + */ + + "pushl %%ebx" "\n\t" + "call _CloseHandle@4" "\n\t" + + /* + * Come here on normal exit. Recover the TCLEXCEPTION_REGISTRATION + * and put a TRUE status return into it. + */ + + "movl %%fs:0, %%edx" "\n\t" + "movl $1, %%eax" "\n\t" + "movl %%eax, 0x10(%%edx)" "\n\t" + "jmp 2f" "\n" + + /* + * Come here on an exception. Recover the TCLEXCEPTION_REGISTRATION + */ + + "1:" "\t" + "movl %%fs:0, %%edx" "\n\t" + "movl 0x8(%%edx), %%edx" "\n\t" + + /* + * Come here however we exited. Restore context from the + * TCLEXCEPTION_REGISTRATION in case the stack is unbalanced. + */ + + "2:" "\t" + "movl 0xC(%%edx), %%esp" "\n\t" + "movl 0x8(%%edx), %%ebp" "\n\t" + "movl 0x0(%%edx), %%eax" "\n\t" + "movl %%eax, %%fs:0" "\n\t" + + : + /* No outputs */ + : + [registration] "m" (registration), + [dupedHandle] "m" (dupedHandle) + : + "%eax", "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory" + ); + result = registration.status; +#else +#ifndef HAVE_NO_SEH + __try { +#endif + CloseHandle(dupedHandle); + result = 1; +#ifndef HAVE_NO_SEH + } __except (EXCEPTION_EXECUTE_HANDLER) {} +#endif +#endif + if (result == FALSE) { + return NULL; + } + + /* + * Fall through, the handle is valid. + * + * Create the undefined channel, anyways, because we know the handle + * is valid to something. + */ + + channel = OpenFileChannel(handle, channelName, mode, 0); + } + + return channel; +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetDefaultStdChannel -- + * + * Constructs a channel for the specified standard OS handle. + * + * Results: + * Returns the specified default standard channel, or NULL. + * + * Side effects: + * May cause the creation of a standard channel and the underlying file. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +TclpGetDefaultStdChannel( + int type) /* One of TCL_STDIN, TCL_STDOUT, or + * TCL_STDERR. */ +{ + Tcl_Channel channel; + HANDLE handle; + int mode = -1; + const char *bufMode = NULL; + DWORD handleId = (DWORD) -1; + /* Standard handle to retrieve. */ + + switch (type) { + case TCL_STDIN: + handleId = STD_INPUT_HANDLE; + mode = TCL_READABLE; + bufMode = "line"; + break; + case TCL_STDOUT: + handleId = STD_OUTPUT_HANDLE; + mode = TCL_WRITABLE; + bufMode = "line"; + break; + case TCL_STDERR: + handleId = STD_ERROR_HANDLE; + mode = TCL_WRITABLE; + bufMode = "none"; + break; + default: + Tcl_Panic("TclGetDefaultStdChannel: Unexpected channel type"); + break; + } + + handle = GetStdHandle(handleId); + + /* + * Note that we need to check for 0 because Windows may return 0 if this + * is not a console mode application, even though this is not a valid + * handle. + */ + + if ((handle == INVALID_HANDLE_VALUE) || (handle == 0)) { + return (Tcl_Channel) NULL; + } + + channel = Tcl_MakeFileChannel(handle, mode); + + if (channel == NULL) { + return (Tcl_Channel) NULL; + } + + /* + * Set up the normal channel options for stdio handles. + */ + + if (Tcl_SetChannelOption(NULL,channel,"-translation","auto")!=TCL_OK || + Tcl_SetChannelOption(NULL,channel,"-buffering",bufMode)!=TCL_OK) { + Tcl_CloseEx(NULL, channel, 0); + return (Tcl_Channel) NULL; + } + return channel; +} + +/* + *---------------------------------------------------------------------- + * + * OpenFileChannel -- + * + * Constructs a File channel for the specified standard OS handle. This + * is a helper function to break up the construction of channels into + * File, Console, or Serial. + * + * Results: + * Returns the new channel, or NULL. + * + * Side effects: + * May open the channel and may cause creation of a file on the file + * system. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +OpenFileChannel( + HANDLE handle, /* Win32 HANDLE to swallow */ + char *channelName, /* Buffer to receive channel name */ + int permissions, /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, or TCL_EXCEPTION, indicating + * which operations are valid on the file. */ + int appendMode) /* OR'ed combination of bits indicating what + * additional configuration of the channel is + * present. */ +{ + FileInfo *infoPtr; + ThreadSpecificData *tsdPtr = FileInit(); + + /* + * See if a channel with this handle already exists. + */ + + for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->handle == (HANDLE) handle) { + return ((permissions & (TCL_READABLE|TCL_WRITABLE|TCL_EXCEPTION))==infoPtr->validMask) + ? infoPtr->channel : NULL; + } + } + + infoPtr = (FileInfo *)Tcl_Alloc(sizeof(FileInfo)); + + /* + * TIP #218. Removed the code inserting the new structure into the global + * list. This is now handled in the thread action callbacks, and only + * there. + */ + + infoPtr->nextPtr = NULL; + infoPtr->validMask = permissions & (TCL_READABLE|TCL_WRITABLE|TCL_EXCEPTION); + infoPtr->watchMask = 0; + infoPtr->flags = appendMode; + infoPtr->handle = handle; + infoPtr->dirty = 0; + TclWinGenerateChannelName(channelName, "file", infoPtr); + infoPtr->channel = Tcl_CreateChannel(&fileChannelType, channelName, + infoPtr, permissions); + + /* + * Files have default translation of AUTO and ^Z eof char, which means + * that a ^Z will be accepted as EOF when reading. + */ + + Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); + + return infoPtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * TclWinFlushDirtyChannels -- + * + * Flush all dirty channels to disk, so that requesting the size of any + * file returns the correct value. + * + * Results: + * None. + * + * Side effects: + * Information is actually written to disk now, rather than later. Don't + * call this too often, or there will be a performance hit (i.e. only + * call when we need to ask for the size of a file). + * + *---------------------------------------------------------------------- + */ + +void +TclWinFlushDirtyChannels(void) +{ + FileInfo *infoPtr; + ThreadSpecificData *tsdPtr = FileInit(); + + /* + * Flush all channels which are dirty, i.e. may have data pending in the + * OS. + */ + + for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->dirty) { + FlushFileBuffers(infoPtr->handle); + infoPtr->dirty = 0; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FileThreadActionProc -- + * + * Insert or remove any thread local refs to this channel. + * + * Results: + * None. + * + * Side effects: + * Changes thread local list of valid channels. + * + *---------------------------------------------------------------------- + */ + +static void +FileThreadActionProc( + void *instanceData, + int action) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + FileInfo *infoPtr = (FileInfo *)instanceData; + + if (action == TCL_CHANNEL_THREAD_INSERT) { + infoPtr->nextPtr = tsdPtr->firstFilePtr; + tsdPtr->firstFilePtr = infoPtr; + } else { + FileInfo **nextPtrPtr; + int removed = 0; + + for (nextPtrPtr = &(tsdPtr->firstFilePtr); (*nextPtrPtr) != NULL; + nextPtrPtr = &((*nextPtrPtr)->nextPtr)) { + if ((*nextPtrPtr) == infoPtr) { + (*nextPtrPtr) = infoPtr->nextPtr; + removed = 1; + break; + } + } + + /* + * This could happen if the channel was created in one thread and then + * moved to another without updating the thread local data in each + * thread. + */ + + if (!removed) { + Tcl_Panic("file info ptr not on thread channel list"); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FileGetType -- + * + * Given a file handle, return its type + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +DWORD +FileGetType( + HANDLE handle) /* Opened file handle */ +{ + DWORD type; + + type = GetFileType(handle); + + /* + * If the file is a character device, we need to try to figure out whether + * it is a serial port, a console, or something else. We test for the + * console case first because this is more common. + */ + + if ((type == FILE_TYPE_CHAR) + || ((type == FILE_TYPE_UNKNOWN) && !GetLastError())) { + DWORD consoleParams; + + if (GetConsoleMode(handle, &consoleParams)) { + type = FILE_TYPE_CONSOLE; + } else { + DCB dcb; + + dcb.DCBlength = sizeof(DCB); + if (GetCommState(handle, &dcb)) { + type = FILE_TYPE_SERIAL; + } + } + } + + return type; +} + + /* + *---------------------------------------------------------------------- + * + * NativeIsComPort -- + * + * Determines if a path refers to a Windows serial port. A simple and + * efficient solution is to use a "name hint" to detect COM ports by + * their filename instead of resorting to a syscall to detect serialness + * after the fact. + * + * The following patterns cover common serial port names: + * COM[1-9] + * \\.\COM[0-9]+ + * + * Results: + * 1 = serial port, 0 = not. + * + *---------------------------------------------------------------------- + */ + +static int +NativeIsComPort( + const WCHAR *nativePath) /* Path of file to access, native encoding. */ +{ + const WCHAR *p = (const WCHAR *) nativePath; + size_t i, len = wcslen(p); + + /* + * 1. Look for com[1-9]:? + */ + + if ((len == 4) && (_wcsnicmp(p, L"com", 3) == 0)) { + /* + * The 4th character must be a digit 1..9 + */ + + if ((p[3] < '1') || (p[3] > '9')) { + return 0; + } + return 1; + } + + /* + * 2. Look for \\.\com[0-9]+ + */ + + if ((len >= 8) && (_wcsnicmp(p, L"\\\\.\\com", 7) == 0)) { + /* + * Charaters 8..end must be a digits 0..9 + */ + + for (i=7; i '9')) { + return 0; + } + } + return 1; + } + return 0; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclWinConsole.c b/vendor/tcl/win/tclWinConsole.c new file mode 100644 index 00000000..480af164 --- /dev/null +++ b/vendor/tcl/win/tclWinConsole.c @@ -0,0 +1,2451 @@ +/* + * tclWinConsole.c -- + * + * This file implements the Windows-specific console functions, and the + * "console" channel driver. Windows 7 or later required. + * + * Copyright © 2022 Ashok P. Nadkarni + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#ifdef TCL_CONSOLE_DEBUG +#undef NDEBUG /* Enable asserts */ +#endif + +#include "tclWinInt.h" +#include + +/* + * A general note on the design: The console channel driver differs from + * most other drivers in the following respects: + * + * - There can be at most 3 console handles at any time since Windows does + * support allocation of more than one console (with three handles + * corresponding to stdin, stdout, stderr) + * + * - Consoles are created / inherited at process startup. There is currently + * no way in Tcl to programmatically create a console. Even if these were + * added the above Windows limitation would still apply. + * + * - Unlike files, sockets etc. where there is a one-to-one + * correspondence between Tcl channels and operating system handles, + * std* channels are shared amongst threads which means there can be + * multiple Tcl channels corresponding to a single console handle. + * + * - Even with multiple threads, more than one file event handler is + * unlikely. It does not make sense for multiple threads to register + * handlers for stdin because the input would be randomly fragmented amongst + * the threads. + * + * Various design factors are driven by the above, e.g. use of lists instead + * of hash tables (at most 3 console handles) and use of global instead of + * per thread queues which simplifies lock management particularly because + * thread-console relation is not one-one and is likely more performant as + * well with fewer locks needing to be obtained. + * + * Some additional design notes/reminders for the future: + * + * Aligned, synchronous reads are done directly by interpreter thread. + * Unaligned or asynchronous reads are done through the reader thread. + * + * The reader thread does not read ahead. That is, it will not post a read + * until some interpreter thread is actually requesting a read. This is + * because an interpreter may (for example) turn off echo for passwords and + * the read ahead would come in the way of that. + * + * If multiple threads are reading from stdin, the input is sprayed in + * random fashion. This is not good application design and hence no plan to + * address this (not clear what should be done even in theory). + * + * For output, we do not restrict all output to the console writer threads. + * See ConsoleOutputProc for the conditions. + * + * Locks are never held when calling the ReadConsole/WriteConsole API's + * since they may block. + */ + +static int gInitialized = 0; + +/* + * INPUT_BUFFER_SIZE is size of buffer passed to ReadConsole in bytes. + * Note that ReadConsole will only allow reading of line lengths up to the + * max of 256 and buffer size passed to it. So dropping this below 512 + * means user can type at most 256 chars. + */ +#ifndef INPUT_BUFFER_SIZE +#define INPUT_BUFFER_SIZE 8192 /* In bytes, so 4096 chars */ +#endif + +/* + * CONSOLE_BUFFER_SIZE is size of storage used in ring buffers. + * In theory, at least sizeof(WCHAR) but note the Tcl channel bug + * https://core.tcl-lang.org/tcl/tktview/b3977d199b08e3979a8da970553d5209b3042e9c + * will cause failures in test suite if close to max input line in the suite. + */ +#ifndef CONSOLE_BUFFER_SIZE +#define CONSOLE_BUFFER_SIZE 8192 /* In bytes */ +#endif + +/* + * Ring buffer for storing data. Actual data is from bufPtr[start]:bufPtr[size-1] + * and bufPtr[0]:bufPtr[length - (size-start)]. + */ +typedef struct RingBuffer { + char *bufPtr; /* Pointer to buffer storage */ + Tcl_Size capacity; /* Size of the buffer in RingBufferChar */ + Tcl_Size start; /* Start of the data within the buffer. */ + Tcl_Size length; /* Number of RingBufferChar*/ +} RingBuffer; + +#define RingBufferLength(ringPtr_) \ + ((ringPtr_)->length) +#define RingBufferHasFreeSpace(ringPtr_) \ + ((ringPtr_)->length < (ringPtr_)->capacity) +#define RINGBUFFER_ASSERT(ringPtr_) \ + assert(RingBufferCheck(ringPtr_)) + +/* + * The Win32 console API does not support non-blocking I/O in any form. Thus + * the actual calls are made on a separate thread. Moreover, separate + * threads are needed for each handle because (for example) blocking on user + * input on stdin should not prevent output to stdout when non-blocking i/o + * is configured at the script level. + * + * In the input (e.g. stdin) case, the console stdin thread is the producer + * writing to the buffer ring buffer. The Tcl interpreter threads are the + * consumer. For the output (e.g. stdout/stderr) case, the Tcl interpreter + * are the producers while the console stdout/stderr threads are the + * consumers. + * + * Consoles are identified purely by handles and multiple threads may open + * them (as stdin/stdout/stderr are shared). + * + * Note on reference counting - a ConsoleHandleInfo instance has multiple + * references to it - one each from every channel that is attached to it + * plus one from the console thread itself which also serves as the reference + * from gConsoleHandleInfoList. + */ +typedef struct ConsoleHandleInfo { + struct ConsoleHandleInfo *nextPtr; /* Process-global list of consoles */ + HANDLE console; /* Console handle */ + HANDLE consoleThread; /* Handle to thread doing actual i/o on the + * console */ + SRWLOCK lock; /* Controls access to this structure. Cheaper + * than CRITICAL_SECTION but note does not + * support recursive locks or Try* style + * attempts. */ + CONDITION_VARIABLE consoleThreadCV;/* For awakening console thread */ + CONDITION_VARIABLE interpThreadCV; /* For awakening interpthread(s) */ + RingBuffer buffer; /* Buffer for data transferred between console + * threads and Tcl threads. For input consoles, + * written by the console thread and read by Tcl + * threads. The converse for output threads */ + DWORD initMode; /* Initial console mode. */ + DWORD lastError; /* An error caused by the last background + * operation. Set to 0 if no error has been + * detected. */ + int numRefs; /* See comments above */ + int permissions; /* TCL_READABLE for input consoles, + * TCL_WRITABLE for output. Only one or the + * other can be set. */ + int flags; /* State flags */ +} ConsoleHandleInfo; + +enum ConsoleHandleInfoFlags { + CONSOLE_DATA_AWAITED = 1 /* An interpreter is awaiting data */ +}; + +/* + * This structure describes per-instance data for a console based channel. + * + * Note on locking - this structure has no locks because it is accessed + * only from the thread owning channel EXCEPT when a console traverses it + * looking for a channel that is watching for events on the console. Even + * in that case, no locking is required because that access is only under + * the gConsoleLock lock which prevents the channel from being removed from + * the gWatchingChannelList which in turn means it will not be deallocated + * from under the console thread. Access to individual fields does not need + * to be controlled because + * - the console thread does not write to any fields + * - changes to the nextWatchingChannelPtr field + * - changes to other fields do not matter because after being read for + * queueing events, they are verified again when the event is received + * in the interpreter thread (since they could have changed anyways while + * the event was in-flight on the event queue) + * + * Note on reference counting - a structure instance may be referenced from + * three places: + * - the Tcl channel subsystem. This reference is created when on channel + * opening and dropped on channel close. This also covers the reference + * from gWatchingChannelList since queueing / dequeuing from that list + * happens in conjunction with channel operations. + * - the Tcl event queue entries. This reference is added when the event + * is queued and dropped on receipt. + */ +typedef struct ConsoleChannelInfo { + HANDLE handle; /* Console handle */ + Tcl_ThreadId threadId; /* Id of owning thread */ + struct ConsoleChannelInfo *nextWatchingChannelPtr; + /* Pointer to next channel watching events. */ + Tcl_Channel channel; /* Pointer to channel structure. */ + DWORD initMode; /* Initial console mode. */ + int numRefs; /* See comments above */ + int permissions; /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, or TCL_EXCEPTION: indicates + * which operations are valid on the file. */ + int watchMask; /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, or TCL_EXCEPTION: indicates + * which events should be reported. */ + int flags; /* State flags */ +} ConsoleChannelInfo; + +enum ConsoleChannelInfoFlags { + CONSOLE_EVENT_QUEUED = 1, /* Notification event already queued */ + CONSOLE_ASYNC = 2, /* Channel is non-blocking. */ + CONSOLE_READ_OPS = 4 /* Channel supports read-related ops. */ +}; + +/* + * The following structure is what is added to the Tcl event queue when + * console events are generated. + */ + +typedef struct { + Tcl_Event header; /* Information that is standard for all events. */ + ConsoleChannelInfo *chanInfoPtr; + /* Pointer to console info structure. Note + * that we still have to verify that the + * console exists before dereferencing this + * pointer. */ +} ConsoleEvent; + +/* + * Declarations for functions used only in this file. + */ + +static int ConsoleBlockModeProc(void *instanceData, int mode); +static void ConsoleCheckProc(void *clientData, int flags); +static int ConsoleCloseProc(void *instanceData, + Tcl_Interp *interp, int flags); +static int ConsoleEventProc(Tcl_Event *evPtr, int flags); +static void ConsoleExitHandler(void *clientData); +static int ConsoleGetHandleProc(void *instanceData, + int direction, void **handlePtr); +static int ConsoleGetOptionProc(void *instanceData, + Tcl_Interp *interp, const char *optionName, + Tcl_DString *dsPtr); +static void ConsoleInit(void); +static int ConsoleInputProc(void *instanceData, char *buf, + int toRead, int *errorCode); +static int ConsoleOutputProc(void *instanceData, + const char *buf, int toWrite, int *errorCode); +static int ConsoleSetOptionProc(void *instanceData, + Tcl_Interp *interp, const char *optionName, + const char *value); +static void ConsoleSetupProc(void *clientData, int flags); +static void ConsoleWatchProc(void *instanceData, int mask); +static void ProcExitHandler(void *clientData); +static void ConsoleThreadActionProc(void *instanceData, int action); +static DWORD ReadConsoleChars(HANDLE hConsole, WCHAR *lpBuffer, + Tcl_Size nChars, Tcl_Size *nCharsReadPtr); +static DWORD WriteConsoleChars(HANDLE hConsole, + const WCHAR *lpBuffer, Tcl_Size nChars, + Tcl_Size *nCharsWritten); +static void RingBufferInit(RingBuffer *ringPtr, Tcl_Size capacity); +static void RingBufferClear(RingBuffer *ringPtr); +static Tcl_Size RingBufferIn(RingBuffer *ringPtr, const char *srcPtr, + Tcl_Size srcLen, int partialCopyOk); +static Tcl_Size RingBufferOut(RingBuffer *ringPtr, char *dstPtr, + Tcl_Size dstCapacity, int partialCopyOk); +static ConsoleHandleInfo *AllocateConsoleHandleInfo(HANDLE consoleHandle, + int permissions); +static ConsoleHandleInfo *FindConsoleInfo(const ConsoleChannelInfo *); +static DWORD WINAPI ConsoleReaderThread(LPVOID arg); +static DWORD WINAPI ConsoleWriterThread(LPVOID arg); +static void NudgeWatchers(HANDLE consoleHandle); +#ifndef NDEBUG +static int RingBufferCheck(const RingBuffer *ringPtr); +#endif + +/* + * Static data. + */ + +typedef struct { + /* Currently this struct is only used to detect thread initialization */ + int notUsed; /* Dummy field */ +} ThreadSpecificData; +static Tcl_ThreadDataKey dataKey; + +/* + * All access to static data is controlled through a single process-wide + * lock. A process can have only a single console at a time, with three + * handles for stdin, stdout and stderr. Creation/destruction of consoles is + * a relatively rare event (currently only possible during process start), + * the number of consoles (as opposed to channels) is small (only stdin, + * stdout and stderr), and contention low. More finer-grained locking would + * likely not only complicate implementation but be slower due to multiple + * locks being held. Note console channels also differ from other Tcl + * channel types in that the channel<->OS descriptor mapping is not one-to-one. + */ +SRWLOCK gConsoleLock; + +/* Process-wide list of console handles. Access control through gConsoleLock */ +static ConsoleHandleInfo *gConsoleHandleInfoList; + +/* + * Process-wide list of channels that are listening for events. Again access + * control through gConsoleLock. Common list for all threads is simplifies + * locking and bookkeeping and is workable because in practice multiple + * threads are very unlikely to be all waiting on stdin (not workable + * because input would be randomly distributed to threads) + */ +static ConsoleChannelInfo *gWatchingChannelList; + +/* + * This structure describes the channel type structure for command console + * based IO. + */ + +static const Tcl_ChannelType consoleChannelType = { + "console", + TCL_CHANNEL_VERSION_5, + NULL, /* Deprecated. */ + ConsoleInputProc, + ConsoleOutputProc, + NULL, /* Deprecated. */ + ConsoleSetOptionProc, + ConsoleGetOptionProc, + ConsoleWatchProc, + ConsoleGetHandleProc, + ConsoleCloseProc, + ConsoleBlockModeProc, + NULL, /* Flush proc. */ + NULL, /* Bubbled event handler proc. */ + NULL, /* Seek proc. */ + ConsoleThreadActionProc, + NULL /* Truncation proc. */ +}; + +/* + *------------------------------------------------------------------------ + * + * RingBufferInit -- + * + * Initializes the ring buffer to a given size. + * + * Results: + * None. + * + * Side effects: + * Panics on allocation failure. + * + *------------------------------------------------------------------------ + */ +static void +RingBufferInit( + RingBuffer *ringPtr, + Tcl_Size capacity) +{ + if (capacity <= 0 || capacity > TCL_SIZE_MAX) { + Tcl_Panic("Internal error: invalid ring buffer capacity requested."); + } + ringPtr->bufPtr = (char *)Tcl_Alloc(capacity); + ringPtr->capacity = capacity; + ringPtr->start = 0; + ringPtr->length = 0; +} + +/* + *------------------------------------------------------------------------ + * + * RingBufferClear + * + * Clears the contents of a ring buffer. + * + * Results: + * None. + * + * Side effects: + * The allocated internal buffer is freed. + * + *------------------------------------------------------------------------ + */ +static void +RingBufferClear( + RingBuffer *ringPtr) +{ + if (ringPtr->bufPtr) { + Tcl_Free(ringPtr->bufPtr); + ringPtr->bufPtr = NULL; + } + ringPtr->capacity = 0; + ringPtr->start = 0; + ringPtr->length = 0; +} + +/* + *------------------------------------------------------------------------ + * + * RingBufferIn -- + * + * Appends data to the ring buffer. + * + * Results: + * Returns number of bytes copied. + * + * Side effects: + * Internal buffer is updated. + * + *------------------------------------------------------------------------ + */ +static Tcl_Size +RingBufferIn( + RingBuffer *ringPtr, + const char *srcPtr, /* Source to be copied */ + Tcl_Size srcLen, /* Length of source */ + int partialCopyOk) /* If true, partial copy is permitted */ +{ + Tcl_Size freeSpace; + + RINGBUFFER_ASSERT(ringPtr); + + freeSpace = ringPtr->capacity - ringPtr->length; + if (freeSpace < srcLen) { + if (!partialCopyOk) { + return 0; + } + /* Copy only as much as free space allows */ + srcLen = freeSpace; + } + + if (ringPtr->capacity - ringPtr->start > ringPtr->length) { + /* There is room at the back */ + Tcl_Size endSpaceStart = ringPtr->start + ringPtr->length; + Tcl_Size endSpace = ringPtr->capacity - endSpaceStart; + if (endSpace >= srcLen) { + /* Everything fits at the back */ + memmove(endSpaceStart + ringPtr->bufPtr, srcPtr, srcLen); + } else { + /* srcLen > endSpace */ + memmove(endSpaceStart + ringPtr->bufPtr, srcPtr, endSpace); + memmove(ringPtr->bufPtr, endSpace + srcPtr, srcLen - endSpace); + } + } else { + /* No room at the back. Existing data wrap to front. */ + Tcl_Size wrapLen = + ringPtr->start + ringPtr->length - ringPtr->capacity; + memmove(wrapLen + ringPtr->bufPtr, srcPtr, srcLen); + } + + ringPtr->length += srcLen; + + RINGBUFFER_ASSERT(ringPtr); + + return srcLen; +} + +/* + *------------------------------------------------------------------------ + * + * RingBufferOut -- + * + * Moves data out of the ring buffer. If dstPtr is NULL, the data + * is simply removed. + * + * Results: + * Returns number of bytes copied or removed. + * + * Side effects: + * Internal buffer is updated. + * + *------------------------------------------------------------------------ + */ +static Tcl_Size +RingBufferOut( + RingBuffer *ringPtr, + char *dstPtr, /* Buffer for output data. May be NULL */ + Tcl_Size dstCapacity, /* Size of buffer */ + int partialCopyOk) /* If true, return what's available */ +{ + Tcl_Size leadLen; + + RINGBUFFER_ASSERT(ringPtr); + + if (dstCapacity > ringPtr->length) { + if (dstPtr && !partialCopyOk) { + return 0; + } + dstCapacity = ringPtr->length; + } + + if (ringPtr->start <= (ringPtr->capacity - ringPtr->length)) { + /* No content wrap around. So leadLen is entire content */ + leadLen = ringPtr->length; + } else { + /* Content wraps around so lead segment stretches to end of buffer */ + leadLen = ringPtr->capacity - ringPtr->start; + } + if (leadLen >= dstCapacity) { + if (dstPtr) { + memmove(dstPtr, ringPtr->start + ringPtr->bufPtr, dstCapacity); + } + ringPtr->start += dstCapacity; + } else { + Tcl_Size wrapLen = dstCapacity - leadLen; + if (dstPtr) { + memmove(dstPtr, ringPtr->start + ringPtr->bufPtr, leadLen); + memmove(leadLen + dstPtr, ringPtr->bufPtr, wrapLen); + } + ringPtr->start = wrapLen; + } + + ringPtr->length -= dstCapacity; + if (ringPtr->start == ringPtr->capacity || ringPtr->length == 0) { + ringPtr->start = 0; + } + + RINGBUFFER_ASSERT(ringPtr); + + return dstCapacity; +} + +#ifndef NDEBUG +static int +RingBufferCheck( + const RingBuffer *ringPtr) +{ + return (ringPtr->bufPtr != NULL && ringPtr->capacity == CONSOLE_BUFFER_SIZE + && ringPtr->start < ringPtr->capacity + && ringPtr->length <= ringPtr->capacity); +} +#endif + +/* + *------------------------------------------------------------------------ + * + * ReadConsoleChars -- + * + * Wrapper for ReadConsoleW. + * + * Results: + * Returns 0 on success, else Windows error code. + * + * Side effects: + * On success the number of characters (not bytes) read is stored in + * *nCharsReadPtr. This will be 0 if the operation was interrupted by + * a Ctrl-C or a CancelIo call. + * + *------------------------------------------------------------------------ + */ +static DWORD +ReadConsoleChars( + HANDLE hConsole, + WCHAR *lpBuffer, + Tcl_Size nChars, + Tcl_Size *nCharsReadPtr) +{ + DWORD nRead; + + /* + * If user types a Ctrl-Break or Ctrl-C, ReadConsole will return success + * with ntchars == 0 and GetLastError() will be ERROR_OPERATION_ABORTED. + * If no Ctrl signal handlers have been established, the default signal + * OS handler in a separate thread will terminate the program. If a Ctrl + * signal handler has been established (through an extension for + * example), it will run and take whatever action it deems appropriate. + * + * If one thread closes its channel, it calls CancelSynchronousIo on the + * console handle which results again in success being returned and + * GetLastError() being ERROR_OPERATION_ABORTED but ntchars in + * unmodified. + * + * In both cases above we will return success but with nbytesread as 0. + * This allows caller to check for thread termination etc. + * + * See https://bugs.python.org/issue30237 + * or https://github.com/microsoft/terminal/issues/12143 + */ + nRead = (DWORD)-1; + if (!ReadConsoleW(hConsole, lpBuffer, nChars, &nRead, NULL)) { + return GetLastError(); + } + if ((nRead == 0 || nRead == (DWORD)-1) + && GetLastError() == ERROR_OPERATION_ABORTED) { + nRead = 0; + } + *nCharsReadPtr = nRead; + return 0; +} + +/* + *------------------------------------------------------------------------ + * + * WriteConsoleChars -- + * + * Wrapper for WriteConsoleW. + * + * Results: + * Returns 0 on success, Windows error code on failure. + * + * Side effects: + * On success the number of characters (not bytes) written is stored in + * *nCharsWrittenPtr. This will be 0 if the operation was interrupted by + * a Ctrl-C or a CancelIo call. + * + *------------------------------------------------------------------------ + */ + +static DWORD +WriteConsoleChars( + HANDLE hConsole, + const WCHAR *lpBuffer, + Tcl_Size nChars, + Tcl_Size *nCharsWrittenPtr) +{ + DWORD nCharsWritten; + + /* See comments in ReadConsoleChars, not sure that applies here */ + nCharsWritten = (DWORD)-1; + if (!WriteConsoleW(hConsole, lpBuffer, nChars, &nCharsWritten, NULL)) { + return GetLastError(); + } + if (nCharsWritten == (DWORD) -1) { + nCharsWritten = 0; + } + *nCharsWrittenPtr = nCharsWritten; + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleInit -- + * + * This function initializes the static variables for this file. + * + * Results: + * None. + * + * Side effects: + * Creates a new event source. + * + *---------------------------------------------------------------------- + */ + +static void +ConsoleInit(void) +{ + /* + * Check the initialized flag first, then check again in the mutex. This + * is a speed enhancement. + */ + + if (!gInitialized) { + AcquireSRWLockExclusive(&gConsoleLock); + if (!gInitialized) { + gInitialized = 1; + Tcl_CreateExitHandler(ProcExitHandler, NULL); + } + ReleaseSRWLockExclusive(&gConsoleLock); + } + + if (TclThreadDataKeyGet(&dataKey) == NULL) { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + tsdPtr->notUsed = 0; + Tcl_CreateEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL); + Tcl_CreateThreadExitHandler(ConsoleExitHandler, NULL); + } +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleExitHandler -- + * + * This function is called to cleanup the console module before Tcl is + * unloaded. + * + * Results: + * None. + * + * Side effects: + * Removes the console event source. + * + *---------------------------------------------------------------------- + */ + +static void +ConsoleExitHandler( + TCL_UNUSED(void *)) +{ + Tcl_DeleteEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * ProcExitHandler -- + * + * This function is called to cleanup the process list before Tcl is + * unloaded. + * + * Results: + * None. + * + * Side effects: + * Resets the process list. + * + *---------------------------------------------------------------------- + */ + +static void +ProcExitHandler( + TCL_UNUSED(void *)) +{ + AcquireSRWLockExclusive(&gConsoleLock); + gInitialized = 0; + ReleaseSRWLockExclusive(&gConsoleLock); +} + +/* + *------------------------------------------------------------------------ + * + * NudgeWatchers -- + * + * Wakes up all threads which have file event watchers on the passed + * console handle. + * + * The function locks and releases gConsoleLock. + * Caller must not be holding locks that will violate lock hierarchy. + * + * Results: + * None. + * + * Side effects: + * As above. + *------------------------------------------------------------------------ + */ +static void +NudgeWatchers( + HANDLE consoleHandle) +{ + ConsoleChannelInfo *chanInfoPtr; + AcquireSRWLockShared(&gConsoleLock); /* Shared-read lock */ + for (chanInfoPtr = gWatchingChannelList; chanInfoPtr; + chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) { + /* + * Notify channels interested in our handle AND that have + * a thread attached. + * No lock needed for chanInfoPtr. See ConsoleChannelInfo. + */ + if (chanInfoPtr->handle == consoleHandle + && chanInfoPtr->threadId != NULL) { + Tcl_ThreadAlert(chanInfoPtr->threadId); + } + } + ReleaseSRWLockShared(&gConsoleLock); +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleSetupProc -- + * + * This procedure is invoked before Tcl_DoOneEvent blocks waiting for an + * event. It walks the channel list and if any input channel has data + * available or output channel has space for data, sets the event loop + * blocking time to 0 so that it will poll immediately. + * + * Results: + * None. + * + * Side effects: + * Adjusts the block time if needed. + * + *---------------------------------------------------------------------- + */ + +void +ConsoleSetupProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + ConsoleChannelInfo *chanInfoPtr; + Tcl_Time blockTime = { 0, 0 }; + int block = 1; + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + /* + * Walk the list of channels. See general comments for struct + * ConsoleChannelInfo with regard to locking and field access. + */ + AcquireSRWLockShared(&gConsoleLock); /* READ lock - no data modification */ + + for (chanInfoPtr = gWatchingChannelList; block && chanInfoPtr != NULL; + chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) { + ConsoleHandleInfo *handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr != NULL) { + AcquireSRWLockShared(&handleInfoPtr->lock); + /* Remember at most one of READABLE, WRITABLE set */ + if (chanInfoPtr->watchMask & TCL_READABLE) { + if (RingBufferLength(&handleInfoPtr->buffer) > 0 + || handleInfoPtr->lastError != ERROR_SUCCESS) { + block = 0; /* Input data available */ + } + } else if (chanInfoPtr->watchMask & TCL_WRITABLE) { + if (RingBufferHasFreeSpace(&handleInfoPtr->buffer)) { + /* TCL_WRITABLE */ + block = 0; /* Output space available */ + } + } + ReleaseSRWLockShared(&handleInfoPtr->lock); + } + } + ReleaseSRWLockShared(&gConsoleLock); + + if (!block) { + /* At least one channel is readable/writable. Set block time to 0 */ + Tcl_SetMaxBlockTime(&blockTime); + } +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleCheckProc -- + * + * This procedure is called by Tcl_DoOneEvent to check the console event + * source for events. + * + * Results: + * None. + * + * Side effects: + * May queue an event. + * + *---------------------------------------------------------------------- + */ + +static void +ConsoleCheckProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + ConsoleChannelInfo *chanInfoPtr; + Tcl_ThreadId me; + int needEvent; + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + me = Tcl_GetCurrentThread(); + + /* + * Acquire a shared lock. Note this is ok even though we potentially + * modify the chanInfoPtr->flags because chanInfoPtr is only modified + * when it belongs to this thread and no other thread will write to it. + * THe shared lock is intended to protect the global gWatchingChannelList + * as we traverse it. + */ + AcquireSRWLockShared(&gConsoleLock); + + for (chanInfoPtr = gWatchingChannelList; chanInfoPtr != NULL; + chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) { + ConsoleHandleInfo *handleInfoPtr; + + if (chanInfoPtr->threadId != me) { + /* Some other thread owns the channel */ + continue; + } + if (chanInfoPtr->flags & CONSOLE_EVENT_QUEUED) { + /* A notification event already queued. No point in another. */ + continue; + } + + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + /* Pointer is safe to access as we are holding gConsoleLock */ + + if (handleInfoPtr == NULL) { + /* Stale event */ + continue; + } + + needEvent = 0; + AcquireSRWLockShared(&handleInfoPtr->lock); + /* Rememeber channel is read or write, never both */ + if (chanInfoPtr->watchMask & TCL_READABLE) { + if (RingBufferLength(&handleInfoPtr->buffer) > 0 + || handleInfoPtr->lastError != ERROR_SUCCESS) { + needEvent = 1; /* Input data available or error/EOF */ + } + /* + * TCL_READABLE watch means someone is looking out for data being + * available, let reader thread know. Note channel need not be + * ASYNC! (Bug [baa51423c2]) + */ + handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + } else if (chanInfoPtr->watchMask & TCL_WRITABLE) { + if (RingBufferHasFreeSpace(&handleInfoPtr->buffer)) { + needEvent = 1; /* Output space available */ + } + } + ReleaseSRWLockShared(&handleInfoPtr->lock); + + if (needEvent) { + ConsoleEvent *evPtr = (ConsoleEvent *)Tcl_Alloc(sizeof(ConsoleEvent)); + + /* See note above loop why this can be accessed without locks */ + chanInfoPtr->flags |= CONSOLE_EVENT_QUEUED; + chanInfoPtr->numRefs += 1; /* So it does not go away while event + * is in queue */ + evPtr->header.proc = ConsoleEventProc; + evPtr->chanInfoPtr = chanInfoPtr; + Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); + } + } + + ReleaseSRWLockShared(&gConsoleLock); +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleBlockModeProc -- + * + * Set blocking or non-blocking mode on channel. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or non-blocking mode. + * + *---------------------------------------------------------------------- + */ + +static int +ConsoleBlockModeProc( + void *instanceData, /* Instance data for channel. */ + int mode) /* TCL_MODE_BLOCKING or + * TCL_MODE_NONBLOCKING. */ +{ + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + + /* + * Consoles on Windows can not be switched between blocking and + * nonblocking, hence we have to emulate the behavior. This is done in the + * input function by checking against a bit in the state. We set or unset + * the bit here to cause the input function to emulate the correct + * behavior. + */ + + if (mode == TCL_MODE_NONBLOCKING) { + chanInfoPtr->flags |= CONSOLE_ASYNC; + } else { + chanInfoPtr->flags &= ~CONSOLE_ASYNC; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleCloseProc -- + * + * Closes a console based IO channel. + * + * Results: + * 0 on success, errno otherwise. + * + * Side effects: + * Closes the physical channel. + * + *---------------------------------------------------------------------- + */ + +static int +ConsoleCloseProc( + void *instanceData, /* Pointer to ConsoleChannelInfo structure. */ + TCL_UNUSED(Tcl_Interp *), + int flags) +{ + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + ConsoleHandleInfo *handleInfoPtr; + int errorCode = 0; + ConsoleChannelInfo **nextPtrPtr; + int closeHandle; + + if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { + return EINVAL; + } + /* + * Don't close the Win32 handle if the handle is a standard channel + * during the thread exit process. Otherwise, one thread may kill the + * stdio of another while exiting. Note an explicit close in script will + * still close the handle. That's historical behavior on all platforms. + */ + if (!TclInThreadExit() + || ( (GetStdHandle(STD_INPUT_HANDLE) != chanInfoPtr->handle) + && (GetStdHandle(STD_OUTPUT_HANDLE) != chanInfoPtr->handle) + && (GetStdHandle(STD_ERROR_HANDLE) != chanInfoPtr->handle))) { + closeHandle = 1; + } else { + closeHandle = 0; + } + + AcquireSRWLockExclusive(&gConsoleLock); + + /* Remove channel from watchers' list */ + for (nextPtrPtr = &gWatchingChannelList; *nextPtrPtr != NULL; + nextPtrPtr = &(*nextPtrPtr)->nextWatchingChannelPtr) { + if (*nextPtrPtr == (ConsoleChannelInfo *) chanInfoPtr) { + *nextPtrPtr = (*nextPtrPtr)->nextWatchingChannelPtr; + break; + } + } + + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr) { + /* + * Console thread may be blocked either waiting for console i/o + * or waiting on the condition variable for buffer empty/full + */ + AcquireSRWLockShared(&handleInfoPtr->lock); + + if (closeHandle) { + handleInfoPtr->console = INVALID_HANDLE_VALUE; + } + + /* Break the thread out of blocking console i/o */ + handleInfoPtr->numRefs -= 1; /* Remove reference from this channel */ + if (handleInfoPtr->numRefs == 1) { + /* + * Abort the i/o if no other threads are listening on it. + * Note without this check, an input line will be skipped on + * the cancel. + */ + CancelSynchronousIo(handleInfoPtr->consoleThread); + } + + /* + * Wake up the console handling thread. Note we do not explicitly + * tell it handle is closed (below). It will find out on next access + */ + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + + ReleaseSRWLockShared(&handleInfoPtr->lock); + } + + ReleaseSRWLockExclusive(&gConsoleLock); + + chanInfoPtr->channel = NULL; + chanInfoPtr->watchMask = 0; + chanInfoPtr->permissions = 0; + + if (closeHandle && chanInfoPtr->handle != INVALID_HANDLE_VALUE) { + if (CloseHandle(chanInfoPtr->handle) == FALSE) { + Tcl_WinConvertError(GetLastError()); + errorCode = errno; + } + chanInfoPtr->handle = INVALID_HANDLE_VALUE; + } + + /* + * Note, we can check and manipulate numRefs without a lock because + * we have removed it from the watch queue so the console thread cannot + * get at it. + */ + if (chanInfoPtr->numRefs > 1) { + /* There may be references already on the event queue */ + chanInfoPtr->numRefs -= 1; + } else { + Tcl_Free(chanInfoPtr); + } + + return errorCode; +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleInputProc -- + * + * Reads input from the IO channel into the buffer given. Returns count + * of how many bytes were actually read, and an error indication. + * + * Results: + * A count of how many bytes were read is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Reads input from the actual channel. + * + *---------------------------------------------------------------------- + */ +static int +ConsoleInputProc( + void *instanceData, /* Console state. */ + char *bufPtr, /* Where to store data read. */ + int bufSize, /* How much space is available in the + * buffer? */ + int *errorCode) /* Where to store error code. */ +{ + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + ConsoleHandleInfo *handleInfoPtr; + Tcl_Size numRead; + + if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) { + return 0; /* EOF */ + } + + *errorCode = 0; + + AcquireSRWLockShared(&gConsoleLock); + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr == NULL) { + /* Really shouldn't happen since channel is holding a reference */ + ReleaseSRWLockShared(&gConsoleLock); + return 0; /* EOF */ + } + AcquireSRWLockExclusive(&handleInfoPtr->lock); + ReleaseSRWLockShared(&gConsoleLock); /* AFTER acquiring handleInfoPtr->lock */ + + while (1) { + numRead = RingBufferOut(&handleInfoPtr->buffer, bufPtr, bufSize, 1); + /* + * Note: even if channel is closed or has an error, as long there is + * buffered data, we will pass it up. + */ + if (numRead != 0) { + break; + } + /* + * No data available. + * - If an error was recorded, generate that and reset it. + * - If EOF, indicate as much. It is up to the application to close + * the channel. + * - Otherwise, if non-blocking return EAGAIN or wait for more data. + */ + if (handleInfoPtr->lastError != 0) { + if (handleInfoPtr->lastError == ERROR_INVALID_HANDLE) { + numRead = 0; /* Treat as EOF */ + } else { + Tcl_WinConvertError(handleInfoPtr->lastError); + handleInfoPtr->lastError = 0; + *errorCode = Tcl_GetErrno(); + numRead = -1; + } + break; + } + if (handleInfoPtr->console == INVALID_HANDLE_VALUE) { + /* EOF - break with numRead == 0 */ + chanInfoPtr->handle = INVALID_HANDLE_VALUE; + break; + } + + /* For async, tell caller we are blocked */ + if (chanInfoPtr->flags & CONSOLE_ASYNC) { + *errorCode = EWOULDBLOCK; + numRead = -1; + break; + } + + /* + * Blocking read. Just get data from directly from console. There + * is a small complication in that + * 1. The destination buffer should be WCHAR aligned. + * 2. We can only read even number of bytes (wide-character API). + * 3. Caller has large enough buffer (else length of line user can + * enter will be limited) + * If any condition is not met, we defer to the + * reader thread which handles these cases rather than dealing with + * them here (which is a little trickier than it might sound.) + * + * TODO - not clear this block is a useful optimization. bufSize by + * default is 4K which is < INPUT_BUFFER_SIZE and will rarely be + * increased on stdin. + */ + if ((1 & (size_t)bufPtr) == 0 /* aligned buffer */ + && (1 & bufSize) == 0 /* Even number of bytes */ + && bufSize > INPUT_BUFFER_SIZE) { + DWORD lastError; + Tcl_Size numChars; + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + lastError = ReadConsoleChars(chanInfoPtr->handle, + (WCHAR *)bufPtr, bufSize / sizeof(WCHAR), &numChars); + /* NOTE lock released so DON'T break. Return instead */ + if (lastError != ERROR_SUCCESS) { + Tcl_WinConvertError(lastError); + *errorCode = Tcl_GetErrno(); + return -1; + } else if (numChars > 0) { + /* Successfully read something. */ + return numChars * sizeof(WCHAR); + } else { + /* + * Ctrl-C/Ctrl-Brk interrupt. Loop around to retry. + * We have to reacquire the lock. No worried about handleInfoPtr + * having gone away since the channel holds a reference. + */ + AcquireSRWLockExclusive(&handleInfoPtr->lock); + continue; + } + } + /* + * Deferring blocking read to reader thread. + * Release the lock and sleep. Note that because the channel + * holds a reference count on handleInfoPtr, it will not + * be deallocated while the lock is released. + */ + handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + if (!SleepConditionVariableSRW(&handleInfoPtr->interpThreadCV, + &handleInfoPtr->lock, INFINITE, 0)) { + Tcl_WinConvertError(GetLastError()); + *errorCode = Tcl_GetErrno(); + numRead = -1; + break; + } + + /* Lock is reacquired, loop back to try again */ + } + + /* We read data. Ask for more if either async or watching for reads */ + if ((chanInfoPtr->flags & CONSOLE_ASYNC) + || (chanInfoPtr->watchMask & TCL_READABLE)) { + handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + } + + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + return numRead; +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleOutputProc -- + * + * Writes the given output on the IO channel. Returns count of how many + * characters were actually written, and an error indication. + * + * Results: + * A count of how many characters were written is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Writes output on the actual channel. + * + *---------------------------------------------------------------------- + */ +static int +ConsoleOutputProc( + void *instanceData, /* Console state. */ + const char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCode) /* Where to store error code. */ +{ + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + ConsoleHandleInfo *handleInfoPtr; + Tcl_Size numWritten; + + *errorCode = 0; + + if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) { + /* Some other thread would have *previously* closed the stdio handle */ + *errorCode = EPIPE; + return -1; + } + + AcquireSRWLockShared(&gConsoleLock); + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr == NULL) { + /* Really shouldn't happen since channel is holding a reference */ + *errorCode = EPIPE; + ReleaseSRWLockShared(&gConsoleLock); + return -1; + } + AcquireSRWLockExclusive(&handleInfoPtr->lock); + ReleaseSRWLockShared(&gConsoleLock); /* AFTER acquiring handleInfoPtr->lock */ + + /* Keep looping until all written. Break out for async and errors */ + numWritten = 0; + while (1) { + /* Check for error and closing on every loop. */ + if (handleInfoPtr->lastError != 0) { + Tcl_WinConvertError(handleInfoPtr->lastError); + *errorCode = Tcl_GetErrno(); + numWritten = -1; + break; + } + if (handleInfoPtr->console == INVALID_HANDLE_VALUE) { + *errorCode = EPIPE; + chanInfoPtr->handle = INVALID_HANDLE_VALUE; + numWritten = -1; + break; + } + + /* + * We can either write directly or through the console thread's + * ring buffer. We have to do the latter when + * (1) the operation is async since WriteConsoleChars is always blocking + * (2) when there is already data in the ring buffer because we don't + * want to reorder output from within a thread + * (3) when there are an odd number of bytes since WriteConsole + * takes whole WCHARs + * (4) when the pointer is not aligned on WCHAR + * The ring buffer deals with cases (3) and (4). It would be harder + * to duplicate that here. + */ + if ((chanInfoPtr->flags & CONSOLE_ASYNC) /* Case (1) */ + || RingBufferLength(&handleInfoPtr->buffer) != 0 /* Case (2) */ + || (toWrite & 1) != 0 /* Case (3) */ + || (PTR2INT(buf) & 1) != 0) { /* Case (4) */ + numWritten += RingBufferIn(&handleInfoPtr->buffer, + numWritten + buf, toWrite - numWritten, 1); + if (numWritten == toWrite || chanInfoPtr->flags & CONSOLE_ASYNC) { + /* All done or async, just accept whatever was written */ + break; + } + /* + * Release the lock and sleep. Note that because the channel + * holds a reference count on handleInfoPtr, it will not + * be deallocated while the lock is released. + */ + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + if (!SleepConditionVariableSRW(&handleInfoPtr->interpThreadCV, + &handleInfoPtr->lock, INFINITE, 0)) { + /* Report the error */ + Tcl_WinConvertError(GetLastError()); + *errorCode = Tcl_GetErrno(); + numWritten = -1; + break; + } + } else { + /* Direct output */ + DWORD winStatus; + HANDLE consoleHandle = handleInfoPtr->console; + /* Unlock before blocking in WriteConsole */ + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + /* UNLOCKED so return, DON'T break out of loop as it will unlock + * again! */ + winStatus = WriteConsoleChars(consoleHandle, + (WCHAR *)buf, toWrite / sizeof(WCHAR), &numWritten); + if (winStatus == ERROR_SUCCESS) { + return numWritten * sizeof(WCHAR); + } else { + Tcl_WinConvertError(winStatus); + *errorCode = Tcl_GetErrno(); + return -1; + } + } + + /* Lock must have been reacquired before continuing loop */ + } + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + return numWritten; +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleEventProc -- + * + * This function is invoked by Tcl_ServiceEvent when a file event reaches + * the front of the event queue. This procedure invokes Tcl_NotifyChannel + * on the console. + * + * Results: + * Returns 1 if the event was handled, meaning it should be removed from + * the queue. Returns 0 if the event was not handled, meaning it should + * stay on the queue. The only time the event isn't handled is if the + * TCL_FILE_EVENTS flag bit isn't set. + * + * Side effects: + * Whatever the notifier callback does. + * + *---------------------------------------------------------------------- + */ + +static int +ConsoleEventProc( + Tcl_Event *evPtr, /* Event to service. */ + int flags) /* Flags that indicate what events to handle, + * such as TCL_FILE_EVENTS. */ +{ + ConsoleEvent *consoleEvPtr = (ConsoleEvent *) evPtr; + ConsoleChannelInfo *chanInfoPtr; + int freeChannel; + int mask = 0; + + if (!(flags & TCL_FILE_EVENTS)) { + return 0; + } + + chanInfoPtr = consoleEvPtr->chanInfoPtr; + /* + * We know chanInfoPtr is valid because its reference count would have + * been incremented when the event was queued. The corresponding release + * happens in this function. + */ + + /* + * Global lock used for chanInfoPtr. A read (shared) lock suffices + * because all access is within the channel owning thread with the + * exception of watchers which is a read-only access. See comments + * to ConsoleChannelInfo. + */ + AcquireSRWLockShared(&gConsoleLock); + chanInfoPtr->flags &= ~CONSOLE_EVENT_QUEUED; + + /* + * Only handle the event if the Tcl channel has not gone away AND is + * still owned by this thread AND is still watching events. + */ + if (chanInfoPtr->channel && chanInfoPtr->threadId == Tcl_GetCurrentThread() + && (chanInfoPtr->watchMask & (TCL_READABLE|TCL_WRITABLE))) { + ConsoleHandleInfo *handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr == NULL) { + /* Console was closed. EOF->read event only (not write) */ + if (chanInfoPtr->watchMask & TCL_READABLE) { + mask = TCL_READABLE; + } + } else { + AcquireSRWLockShared(&handleInfoPtr->lock); + /* Remember at most one of READABLE, WRITABLE set */ + if ((chanInfoPtr->watchMask & TCL_READABLE) + && RingBufferLength(&handleInfoPtr->buffer)) { + mask = TCL_READABLE; + } else if ((chanInfoPtr->watchMask & TCL_WRITABLE) + && RingBufferHasFreeSpace(&handleInfoPtr->buffer)) { + /* Generate write event space available */ + mask = TCL_WRITABLE; + } + ReleaseSRWLockShared(&handleInfoPtr->lock); + } + } + + /* + * Tcl_NotifyChannel can recurse through the file event callback so need + * to release locks first. Our reference still holds so no danger of + * chanInfoPtr being deallocated if the callback closes the channel. + */ + ReleaseSRWLockShared(&gConsoleLock); + if (mask) { + Tcl_NotifyChannel(chanInfoPtr->channel, mask); + /* Note: chanInfoPtr ref count may have changed */ + } + + /* No need to lock - see comments earlier */ + + /* Remove the reference to the channel from event record */ + if (chanInfoPtr->numRefs > 1) { + chanInfoPtr->numRefs -= 1; + freeChannel = 0; + } else { + assert(chanInfoPtr->channel == NULL); + freeChannel = 1; + } + + if (freeChannel) { + Tcl_Free(chanInfoPtr); + } + + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleWatchProc -- + * + * Called by the notifier to set up to watch for events on this channel. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +ConsoleWatchProc( + void *instanceData, /* Console state. */ + int newMask) /* What events to watch for, one of + * TCL_READABLE, TCL_WRITABLE */ +{ + ConsoleChannelInfo **nextPtrPtr, *ptr; + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + int oldMask = chanInfoPtr->watchMask; + + /* + * Since most of the work is handled by the background threads, we just + * need to update the watchMask and then force the notifier to poll once. + */ + + chanInfoPtr->watchMask = newMask & chanInfoPtr->permissions; + if (chanInfoPtr->watchMask) { + Tcl_Time blockTime = { 0, 0 }; + + if (!oldMask) { + AcquireSRWLockExclusive(&gConsoleLock); + /* Add to list of watched channels */ + chanInfoPtr->nextWatchingChannelPtr = gWatchingChannelList; + gWatchingChannelList = chanInfoPtr; + + /* + * For read channels, need to tell the console reader thread + * that we are looking for data since it will not do reads until + * it knows someone is awaiting. + */ + ConsoleHandleInfo *handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr) { + AcquireSRWLockExclusive(&handleInfoPtr->lock); + handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; + WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + } + ReleaseSRWLockExclusive(&gConsoleLock); + } + Tcl_SetMaxBlockTime(&blockTime); + } else if (oldMask) { + /* Remove from list of watched channels */ + + AcquireSRWLockExclusive(&gConsoleLock); + for (nextPtrPtr = &gWatchingChannelList, ptr = *nextPtrPtr; + ptr != NULL; + nextPtrPtr = &ptr->nextWatchingChannelPtr, ptr = *nextPtrPtr) { + if (chanInfoPtr == ptr) { + *nextPtrPtr = ptr->nextWatchingChannelPtr; + break; + } + } + ReleaseSRWLockExclusive(&gConsoleLock); + } +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleGetHandleProc -- + * + * Called from Tcl_GetChannelHandle to retrieve OS handles from inside a + * command consoleline based channel. + * + * Results: + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no + * handle for the specified direction. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +ConsoleGetHandleProc( + void *instanceData, /* The console state. */ + TCL_UNUSED(int) /*direction*/, + void **handlePtr) /* Where to store the handle. */ +{ + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + + if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) { + return TCL_ERROR; + } + *handlePtr = chanInfoPtr->handle; + return TCL_OK; +} + +/* + *------------------------------------------------------------------------ + * + * ConsoleDataAvailable -- + * + * Checks if there is data in the console input queue. + * + * Results: + * Returns 1 if the input queue has data, -1 on error else 0 if empty. + * + * Side effects: + * None. + * + *------------------------------------------------------------------------ + */ +static int +ConsoleDataAvailable( + HANDLE consoleHandle) +{ + INPUT_RECORD input[10]; + DWORD count; + DWORD i; + + /* + * Need at least one keyboard event. + */ + if (PeekConsoleInputW(consoleHandle, input, + sizeof(input) / sizeof(input[0]), &count) == FALSE) { + return -1; + } + /* + * Even if windows size and mouse events are disabled, can still have + * events other than keyboard, like focus events. Look for at least one + * keydown event because a trailing LF keyup is always present from the + * last input. However, if our buffer is full, assume there is a key + * down somewhere in the unread buffer. I suppose we could expand the + * buffer but not worth... + */ + if (count == (sizeof(input)/sizeof(input[0]))) { + return 1; + } + for (i = 0; i < count; ++i) { + if (input[i].EventType == KEY_EVENT + && input[i].Event.KeyEvent.bKeyDown) { + return 1; + } + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleReaderThread -- + * + * This function runs in a separate thread and waits for input to become + * available on a console. + * + * Results: + * Always 0. + * + * Side effects: + * Signals the main thread when input become available. + * + *---------------------------------------------------------------------- + */ +static DWORD WINAPI +ConsoleReaderThread( + LPVOID arg) +{ + ConsoleHandleInfo *handleInfoPtr = (ConsoleHandleInfo *) arg; + ConsoleHandleInfo **iterator; + Tcl_Size inputLen = 0; + Tcl_Size inputOffset = 0; + Tcl_Size lastReadSize = 0; + DWORD sleepTime; + char inputChars[INPUT_BUFFER_SIZE]; + + /* + * Keep looping until one of the following happens. + * - there are no more channels listening on the console + * - the console handle has been closed + */ + + /* This thread is holding a reference so pointer is safe */ + AcquireSRWLockExclusive(&handleInfoPtr->lock); + + while (1) { + + if (handleInfoPtr->numRefs == 1) { + /* + * Sole reference. That's this thread. Exit since no clients + * and no way for a thread to attach to a console after process + * start. + */ + break; + } + + /* + * Shared buffer has no data. If we have some in our private buffer + * copy that. Else check if there has been an error. In both cases + * notify the interp threads. + */ + if (inputLen > 0 || handleInfoPtr->lastError != 0) { + HANDLE consoleHandle; + if (inputLen > 0) { + /* Private buffer has data. Copy it over. */ + Tcl_Size nStored; + + assert((inputLen - inputOffset) > 0); + nStored = RingBufferIn(&handleInfoPtr->buffer, + inputOffset + inputChars, inputLen - inputOffset, + 1); + inputOffset += nStored; + if (inputOffset == inputLen) { + /* Temp buffer now empty */ + inputOffset = 0; + inputLen = 0; + } + } else { + /* + * On error, nothing but inform caller and wait + * We do not want to exit until there are no client interps. + */ + } + + /* + * Wake up any threads waiting either synchronously or + * asynchronously. Since we are providing data, turn off the + * AWAITED flag. If the data provided is not sufficient the + * clients will request again. Note we have to wake up ALL + * awaiting threads, not just one, so they can all reissue + * requests if needed. (In a properly designed app, at most one + * thread should be reading standard input but...) + */ + handleInfoPtr->flags &= ~CONSOLE_DATA_AWAITED; + /* Wake synchronous channels */ + WakeAllConditionVariable(&handleInfoPtr->interpThreadCV); + /* + * Wake up async channels registered for file events. Note in + * order to follow the locking hierarchy, we need to release + * handleInfoPtr->lock before calling NudgeWatchers. + */ + consoleHandle = handleInfoPtr->console; + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + NudgeWatchers(consoleHandle); + AcquireSRWLockExclusive(&handleInfoPtr->lock); + + /* + * Loop back to recheck for exit conditions changes while the + * lock was not held. + */ + continue; + } + + assert(inputLen == 0); + + /* + * Read more data in two cases: + * 1. The previous read filled the buffer and there could be more + * data in the console internal *text* buffer. Note + * ConsolePendingInput (checked in ConsoleDataAvailable) will NOT + * show this. It holds input events not yet translated to text. + * 2. Tcl threads want more data AND there is data in the + * ConsolePendingInput buffer. The latter check necessary because + * we do not want to read ahead because the interp thread might + * change the read mode, e.g. turning off echo for password + * input. So only do so if at least one interpreter has requested + * data. + */ + if (lastReadSize == sizeof(inputChars) + || ((handleInfoPtr->flags & CONSOLE_DATA_AWAITED) + && ConsoleDataAvailable(handleInfoPtr->console))) { + DWORD error; + /* Do not hold the lock while blocked in console */ + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + error = ReadConsoleChars(handleInfoPtr->console, + (WCHAR *)inputChars, sizeof(inputChars) / sizeof(WCHAR), + &inputLen); + AcquireSRWLockExclusive(&handleInfoPtr->lock); + if (error == 0) { + inputLen *= sizeof(WCHAR); + lastReadSize = inputLen; + } else { + /* + * We only store the last error. It is up to channel + * handlers whether to close or not in case of errors. + */ + lastReadSize = 0; + handleInfoPtr->lastError = error; + if (handleInfoPtr->lastError == ERROR_INVALID_HANDLE) { + handleInfoPtr->console = INVALID_HANDLE_VALUE; + } + } + } else { + /* + * Either no one was asking for data, or no data was available. + * In the former case, wait until someone wakes us asking for + * data. In the latter case, there is no alternative but to + * poll since ReadConsole does not support async operation. + * So sleep for a short while and loop back to retry. + */ + sleepTime = + handleInfoPtr->flags & CONSOLE_DATA_AWAITED ? 50 : INFINITE; + SleepConditionVariableSRW(&handleInfoPtr->consoleThreadCV, + &handleInfoPtr->lock, sleepTime, 0); + } + + /* Loop again to check for exit or wait for readers to wake us */ + } + + /* + * Exiting: + * - remove the console from global list + * - close the handle if still valid + * - release the structure + * Note there is not need to check for any watchers because we only + * exit when there are no channels open to this console. + */ + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + AcquireSRWLockExclusive(&gConsoleLock); /* Modifying - exclusive lock */ + for (iterator = &gConsoleHandleInfoList; *iterator; + iterator = &(*iterator)->nextPtr) { + if (*iterator == handleInfoPtr) { + *iterator = handleInfoPtr->nextPtr; + break; + } + } + ReleaseSRWLockExclusive(&gConsoleLock); + + /* No need for relocking - no other thread should have access to it now */ + RingBufferClear(&handleInfoPtr->buffer); + + if (handleInfoPtr->console != INVALID_HANDLE_VALUE + && handleInfoPtr->lastError != ERROR_INVALID_HANDLE) { + SetConsoleMode(handleInfoPtr->console, handleInfoPtr->initMode); + /* + * NOTE: we do not call CloseHandle(handleInfoPtr->console) here. + * As per the GetStdHandle documentation, it need not be closed. + * Other components may be directly using it. Note however that + * an explicit chan close script command does close the handle + * for all threads. + */ + } + + Tcl_Free(handleInfoPtr); + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleWriterThread -- + * + * This function runs in a separate thread and writes data onto a + * console. + * + * Results: + * Always returns 0. + * + * Side effects: + * Signals the main thread when an output operation is completed. + * + *---------------------------------------------------------------------- + */ +static DWORD WINAPI +ConsoleWriterThread( + LPVOID arg) +{ + ConsoleHandleInfo *handleInfoPtr = (ConsoleHandleInfo *) arg; + ConsoleHandleInfo **iterator; + BOOL success; + Tcl_Size numBytes; + /* + * This buffer size has no relation really with the size of the shared + * buffer. Could be bigger or smaller. Make larger as multiple threads + * could potentially be writing to it. + */ + char buffer[2*CONSOLE_BUFFER_SIZE]; + + /* + * Keep looping until one of the following happens. + * + * - there are not more channels listening on the console + * - the console handle has been closed + * + * On each iteration, + * - if the channel buffer is empty, wait for some channel writer to write + * - if there is data in our buffer, write it to the console + */ + + /* This thread is holding a reference so pointer is safe */ + AcquireSRWLockExclusive(&handleInfoPtr->lock); + while (1) { + /* handleInfoPtr->lock must be held on entry to loop */ + + int offset; + HANDLE consoleHandle; + + /* + * Sadly, we need to do another copy because do not want to hold + * a lock on handleInfoPtr->buffer while calling WriteConsole as that + * might block. Also, we only want to copy an integral number of + * WCHAR's, i.e. even number of chars so do some length checks up + * front. + */ + numBytes = RingBufferLength(&handleInfoPtr->buffer); + numBytes &= ~1; /* Copy integral number of WCHARs -> even number of bytes */ + if (numBytes == 0) { + /* No data to write */ + if (handleInfoPtr->numRefs == 1) { + /* + * Sole reference. That's this thread. Exit since no clients + * and no buffered output. + */ + break; + } + /* Wake up any threads waiting synchronously. */ + WakeConditionVariable(&handleInfoPtr->interpThreadCV); + success = SleepConditionVariableSRW(&handleInfoPtr->consoleThreadCV, + &handleInfoPtr->lock, INFINITE, 0); + /* Note: lock has been acquired again! */ + if (!success && GetLastError() != ERROR_TIMEOUT) { + /* TODO - what can be done? Should not happen */ + /* For now keep going */ + } + continue; + } + + /* We have data to write */ + if ((size_t)numBytes > (sizeof(buffer) / sizeof(buffer[0]))) { + numBytes = sizeof(buffer); + } + /* No need to check result, we already checked length bytes available */ + RingBufferOut(&handleInfoPtr->buffer, buffer, numBytes, 0); + + consoleHandle = handleInfoPtr->console; + WakeConditionVariable(&handleInfoPtr->interpThreadCV); + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + offset = 0; + while (numBytes > 0) { + Tcl_Size numWChars = numBytes / sizeof(WCHAR); + DWORD status; + status = WriteConsoleChars(handleInfoPtr->console, + (WCHAR *)(offset + buffer), numWChars, &numWChars); + if (status != 0) { + /* Only overwrite if no previous error */ + if (handleInfoPtr->lastError == 0) { + handleInfoPtr->lastError = status; + } + if (status == ERROR_INVALID_HANDLE) { + handleInfoPtr->console = INVALID_HANDLE_VALUE; + } + /* Assume this write is done but keep looping in case + * it is a transient error. Not sure just closing handle + * and exiting thread is a good idea until all references + * from interp threads are gone. + */ + break; + } + numBytes -= numWChars * sizeof(WCHAR); + offset += numWChars * sizeof(WCHAR); + } + + /* Wake up any threads waiting synchronously. */ + WakeConditionVariable(&handleInfoPtr->interpThreadCV); + /* + * Wake up all channels registered for file events. Note in + * order to follow the locking hierarchy, we cannot hold any locks + * when calling NudgeWatchers. + */ + NudgeWatchers(consoleHandle); + + AcquireSRWLockExclusive(&handleInfoPtr->lock); + } + + /* + * Exiting: + * - remove the console from global list + * - release the structure + * NOTE: we do not call CloseHandle(handleInfoPtr->console) here. + * As per the GetStdHandle documentation, it need not be closed. + * Other components may be directly using it. Note however that + * an explicit chan close script command does close the handle + * for all threads. + */ + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + AcquireSRWLockExclusive(&gConsoleLock); /* Modifying - exclusive lock */ + for (iterator = &gConsoleHandleInfoList; *iterator; + iterator = &(*iterator)->nextPtr) { + if (*iterator == handleInfoPtr) { + *iterator = handleInfoPtr->nextPtr; + break; + } + } + ReleaseSRWLockExclusive(&gConsoleLock); + + RingBufferClear(&handleInfoPtr->buffer); + Tcl_Free(handleInfoPtr); + + return 0; +} + +/* + *------------------------------------------------------------------------ + * + * AllocateConsoleHandleInfo -- + * + * Allocates a ConsoleHandleInfo for the passed console handle. As + * a side effect starts a console thread to handle i/o on the handle. + * + * Important: Caller must be holding an EXCLUSIVE lock on gConsoleLock + * when calling this function. The lock continues to be held on return. + * + * Results: + * Pointer to an unlocked ConsoleHandleInfo structure. The reference + * count on the structure is 1. This corresponds to the common reference + * from the console thread and the gConsoleHandleInfoList. Returns NULL + * on error. + * + * Side effects: + * A console reader or writer thread is started. The returned structure + * is placed on the active console handler list gConsoleHandleInfoList. + * + *------------------------------------------------------------------------ + */ +static ConsoleHandleInfo * +AllocateConsoleHandleInfo( + HANDLE consoleHandle, + int permissions) /* TCL_READABLE or TCL_WRITABLE */ +{ + ConsoleHandleInfo *handleInfoPtr; + DWORD consoleMode; + + handleInfoPtr = (ConsoleHandleInfo *)Tcl_Alloc(sizeof(*handleInfoPtr)); + memset(handleInfoPtr, 0, sizeof(*handleInfoPtr)); + handleInfoPtr->console = consoleHandle; + InitializeSRWLock(&handleInfoPtr->lock); + InitializeConditionVariable(&handleInfoPtr->consoleThreadCV); + InitializeConditionVariable(&handleInfoPtr->interpThreadCV); + RingBufferInit(&handleInfoPtr->buffer, CONSOLE_BUFFER_SIZE); + handleInfoPtr->lastError = 0; + handleInfoPtr->permissions = permissions; + handleInfoPtr->numRefs = 1; /* See function header */ + if (permissions == TCL_READABLE) { + GetConsoleMode(consoleHandle, &handleInfoPtr->initMode); + consoleMode = handleInfoPtr->initMode; + consoleMode &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT); + consoleMode |= ENABLE_LINE_INPUT; + SetConsoleMode(consoleHandle, consoleMode); + } + handleInfoPtr->consoleThread = CreateThread( + NULL, /* default security descriptor */ + 2*CONSOLE_BUFFER_SIZE, /* Stack size - gets rounded up to granularity */ + permissions == TCL_READABLE ? ConsoleReaderThread : ConsoleWriterThread, + handleInfoPtr, /* Pass to thread */ + 0, /* Flags - no special cases */ + NULL); /* Don't care about thread id */ + if (handleInfoPtr->consoleThread == NULL) { + /* Note - SRWLock and condition variables do not need finalization */ + RingBufferClear(&handleInfoPtr->buffer); + Tcl_Free(handleInfoPtr); + return NULL; + } + + /* Chain onto global list */ + handleInfoPtr->nextPtr = gConsoleHandleInfoList; + gConsoleHandleInfoList = handleInfoPtr; + + return handleInfoPtr; +} + +/* + *------------------------------------------------------------------------ + * + * FindConsoleInfo -- + * + * Finds the ConsoleHandleInfo record for a given ConsoleChannelInfo. + * The found record must match the console handle. It is the caller's + * responsibility to check the permissions (read/write) in the returned + * ConsoleHandleInfo match permissions in chanInfoPtr. This function does + * not check that. + * + * Important: Caller must be holding an shared or exclusive lock on + * gConsoleMutex. That ensures the returned pointer stays valid on + * return without risk of deallocation by other threads. + * + * Results: + * Pointer to the found ConsoleHandleInfo or NULL if not found + * + * Side effects: + * None. + * + *------------------------------------------------------------------------ + */ +static ConsoleHandleInfo * +FindConsoleInfo( + const ConsoleChannelInfo *chanInfoPtr) +{ + ConsoleHandleInfo *handleInfoPtr; + for (handleInfoPtr = gConsoleHandleInfoList; handleInfoPtr; + handleInfoPtr = handleInfoPtr->nextPtr) { + if (handleInfoPtr->console == chanInfoPtr->handle) { + return handleInfoPtr; + } + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * TclWinOpenConsoleChannel -- + * + * Constructs a Console channel for the specified standard OS handle. + * This is a helper function to break up the construction of channels + * into File, Console, or Serial. + * + * Results: + * Returns the new channel, or NULL. + * + * Side effects: + * May open the channel. + * + *---------------------------------------------------------------------- + */ +Tcl_Channel +TclWinOpenConsoleChannel( + HANDLE handle, + char *channelName, + int permissions) +{ + ConsoleChannelInfo *chanInfoPtr; + ConsoleHandleInfo *handleInfoPtr; + + /* A console handle can either be input or output, not both */ + if (permissions != TCL_READABLE && permissions != TCL_WRITABLE) { + return NULL; + } + + ConsoleInit(); + + chanInfoPtr = (ConsoleChannelInfo *)Tcl_Alloc(sizeof(*chanInfoPtr)); + memset(chanInfoPtr, 0, sizeof(*chanInfoPtr)); + + chanInfoPtr->permissions = permissions; + chanInfoPtr->handle = handle; + chanInfoPtr->channel = (Tcl_Channel) NULL; + + chanInfoPtr->threadId = Tcl_GetCurrentThread(); + + /* + * Use the pointer for the name of the result channel. This keeps the + * channel names unique, since some may share handles (stdin/stdout/stderr + * for instance). + */ + + TclWinGenerateChannelName(channelName, "file", chanInfoPtr); + + if (permissions & TCL_READABLE) { + /* + * Make sure the console input buffer is ready for only character + * input notifications and the buffer is set for line buffering. IOW, + * we only want to catch when complete lines are ready for reading. + */ + + chanInfoPtr->flags |= CONSOLE_READ_OPS; + GetConsoleMode(handle, &chanInfoPtr->initMode); + +#ifdef OBSOLETE + /* Why was priority being set on console input? Code smell */ + SetThreadPriority(infoPtr->reader.thread, THREAD_PRIORITY_HIGHEST); +#endif + } else { + /* Already checked permissions is WRITABLE if not READABLE */ + /* TODO - enable ansi escape processing? */ + } + + /* + * Global lock but that's ok. See comments top of file. Allocations + * will happen only a few times in the life of a process and that too + * generally at start up where only one thread is active. + */ + AcquireSRWLockExclusive(&gConsoleLock); /*Allocate needs exclusive lock */ + + handleInfoPtr = FindConsoleInfo(chanInfoPtr); + if (handleInfoPtr == NULL) { + /* Not found. Allocate one */ + handleInfoPtr = AllocateConsoleHandleInfo(handle, permissions); + } else { + /* Found. Its direction (read/write) better be the same */ + if (handleInfoPtr->permissions != permissions) { + handleInfoPtr = NULL; + } + } + + if (handleInfoPtr == NULL) { + ReleaseSRWLockExclusive(&gConsoleLock); + if (permissions == TCL_READABLE) { + SetConsoleMode(handle, chanInfoPtr->initMode); + } + Tcl_Free(chanInfoPtr); + return NULL; + } + + /* + * There is effectively a reference to this structure from the Tcl + * channel subsystem. So record that. This reference will be dropped + * when the Tcl channel is closed. + */ + chanInfoPtr->numRefs = 1; + + /* + * Need to keep track of number of referencing channels for closing. + * The pointer is safe since there is a reference held to it from + * gConsoleHandleInfoList but still need to lock the structure itself + */ + AcquireSRWLockExclusive(&handleInfoPtr->lock); + handleInfoPtr->numRefs += 1; + ReleaseSRWLockExclusive(&handleInfoPtr->lock); + + ReleaseSRWLockExclusive(&gConsoleLock); + + /* Note Tcl_CreateChannel never fails other than panic on error */ + chanInfoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName, + chanInfoPtr, permissions); + + /* + * Consoles have default translation of auto and ^Z eof char, which means + * that a ^Z will be accepted as EOF when reading. + */ + + Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-translation", "auto"); + Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-encoding", "utf-16"); + return chanInfoPtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleThreadActionProc -- + * + * Insert or remove any thread local refs to this channel. + * + * Results: + * None. + * + * Side effects: + * Changes thread local list of valid channels. + * + *---------------------------------------------------------------------- + */ + +static void +ConsoleThreadActionProc( + void *instanceData, + int action) +{ + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + + /* No need for any locks as no other thread will be writing to it */ + if (action == TCL_CHANNEL_THREAD_INSERT) { + ConsoleInit(); /* Needed to set up event source handlers for this thread */ + chanInfoPtr->threadId = Tcl_GetCurrentThread(); + } else { + chanInfoPtr->threadId = NULL; + } +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleSetOptionProc -- + * + * Sets an option on a channel. + * + * Results: + * A standard Tcl result. Also sets the interp's result on error if + * interp is not NULL. + * + * Side effects: + * May modify an option on a console. Sets Error message if needed (by + * calling Tcl_BadChannelOption). + * + *---------------------------------------------------------------------- + */ +static int +ConsoleSetOptionProc( + void *instanceData, /* File state. */ + Tcl_Interp *interp, /* For error reporting - can be NULL. */ + const char *optionName, /* Which option to set? */ + const char *value) /* New value for option. */ +{ + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + int len = strlen(optionName); + int vlen = strlen(value); + + /* + * Option -inputmode normal|password|raw + */ + + if ((chanInfoPtr->flags & CONSOLE_READ_OPS) && (len > 1) && + (strncmp(optionName, "-inputmode", len) == 0)) { + DWORD mode; + + if (GetConsoleMode(chanInfoPtr->handle, &mode) == 0) { + Tcl_WinConvertError(GetLastError()); + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't read console mode: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + if (strncasecmp(value, "NORMAL", vlen) == 0) { + mode |= + ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; + } else if (strncasecmp(value, "PASSWORD", vlen) == 0) { + mode |= ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT; + mode &= ~ENABLE_ECHO_INPUT; + } else if (strncasecmp(value, "RAW", vlen) == 0) { + mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); + } else if (strncasecmp(value, "RESET", vlen) == 0) { + /* + * Reset to the initial mode, whatever that is. + */ + mode = chanInfoPtr->initMode; + } else { + if (interp) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad mode \"%s\" for -inputmode: must be" + " normal, password, raw, or reset", value)); + Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", + "VALUE", (char *)NULL); + } + return TCL_ERROR; + } + if (SetConsoleMode(chanInfoPtr->handle, mode) == 0) { + Tcl_WinConvertError(GetLastError()); + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't set console mode: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + + return TCL_OK; + } + + if (chanInfoPtr->flags & CONSOLE_READ_OPS) { + return Tcl_BadChannelOption(interp, optionName, "inputmode"); + } else { + return Tcl_BadChannelOption(interp, optionName, ""); + } +} + +/* + *---------------------------------------------------------------------- + * + * ConsoleGetOptionProc -- + * + * Gets a mode associated with an IO channel. If the optionName arg is + * non-NULL, retrieves the value of that option. If the optionName arg is + * NULL, retrieves a list of alternating option names and values for the + * given channel. + * + * Results: + * A standard Tcl result. Also sets the supplied DString to the string + * value of the option(s) returned. Sets error message if needed + * (by calling Tcl_BadChannelOption). + * + *---------------------------------------------------------------------- + */ + +static int +ConsoleGetOptionProc( + void *instanceData, /* File state. */ + Tcl_Interp *interp, /* For error reporting - can be NULL. */ + const char *optionName, /* Option to get. */ + Tcl_DString *dsPtr) /* Where to store value(s). */ +{ + ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + int valid = 0; /* Flag if valid option parsed. */ + unsigned int len; + char buf[TCL_INTEGER_SPACE]; + + if (optionName == NULL) { + len = 0; + } else { + len = strlen(optionName); + } + + /* + * Get option -inputmode + * + * This is a great simplification of the underlying reality, but actually + * represents what almost all scripts really want to know. + */ + + if (chanInfoPtr->flags & CONSOLE_READ_OPS) { + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-inputmode"); + } + if (len==0 || (len>1 && strncmp(optionName, "-inputmode", len)==0)) { + DWORD mode; + + valid = 1; + if (GetConsoleMode(chanInfoPtr->handle, &mode) == 0) { + Tcl_WinConvertError(GetLastError()); + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't read console mode: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + if (mode & ENABLE_LINE_INPUT) { + if (mode & ENABLE_ECHO_INPUT) { + Tcl_DStringAppendElement(dsPtr, "normal"); + } else { + Tcl_DStringAppendElement(dsPtr, "password"); + } + } else { + Tcl_DStringAppendElement(dsPtr, "raw"); + } + } + } else { + /* + * Output channel. Get option -winsize + * Option is readonly and returned by [fconfigure chan -winsize] but not + * returned by [fconfigure chan] without explicit option name. + */ + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-winsize"); + } + + if (len == 0 || (len > 1 && strncmp(optionName, "-winsize", len) == 0)) { + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + + valid = 1; + if (!GetConsoleScreenBufferInfo(chanInfoPtr->handle, + &consoleInfo)) { + Tcl_WinConvertError(GetLastError()); + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't read console size: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + Tcl_DStringStartSublist(dsPtr); + snprintf(buf, sizeof(buf), "%d", + consoleInfo.srWindow.Right - consoleInfo.srWindow.Left + 1); + Tcl_DStringAppendElement(dsPtr, buf); + snprintf(buf, sizeof(buf), "%d", + consoleInfo.srWindow.Bottom - consoleInfo.srWindow.Top + 1); + Tcl_DStringAppendElement(dsPtr, buf); + Tcl_DStringEndSublist(dsPtr); + } + } + + if (valid) { + return TCL_OK; + } + if (chanInfoPtr->flags & CONSOLE_READ_OPS) { + return Tcl_BadChannelOption(interp, optionName, "inputmode"); + } else { + return Tcl_BadChannelOption(interp, optionName, "winsize"); + } +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclWinDde.c b/vendor/tcl/win/tclWinDde.c new file mode 100644 index 00000000..cd5bb1e7 --- /dev/null +++ b/vendor/tcl/win/tclWinDde.c @@ -0,0 +1,1990 @@ +/* + * tclWinDde.c -- + * + * This file provides functions that implement the "send" command, + * allowing commands to be passed from interpreter to interpreter. + * + * Copyright (c) 1997 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. + */ + +#undef STATIC_BUILD +#ifndef USE_TCL_STUBS +# define USE_TCL_STUBS +#endif +#include "tclInt.h" +#include +#include +#include + +#if defined (__clang__) && (__clang_major__ > 20) +#pragma clang diagnostic ignored "-Wc++-keyword" +#endif + +#if !defined(NDEBUG) + /* test POKE server Implemented for debug mode only */ +# undef CBF_FAIL_POKES +# define CBF_FAIL_POKES 0 +#endif + +/* + * The following structure is used to keep track of the interpreters + * registered by this process. + */ + +typedef struct RegisteredInterp { + struct RegisteredInterp *nextPtr; + /* The next interp this application knows + * about. */ + WCHAR *name; /* Interpreter's name (malloc-ed). */ + Tcl_Obj *handlerPtr; /* The server handler command */ + Tcl_Interp *interp; /* The interpreter attached to this name. */ +} RegisteredInterp; + +/* + * Used to keep track of conversations. + */ + +typedef struct Conversation { + struct Conversation *nextPtr; + /* The next conversation in the list. */ + RegisteredInterp *riPtr; /* The info we know about the conversation. */ + HCONV hConv; /* The DDE handle for this conversation. */ + Tcl_Obj *returnPackagePtr; /* The result package for this conversation. */ +} Conversation; + +typedef struct { + Tcl_Interp *interp; + int result; + ATOM service; + ATOM topic; + HWND hwnd; +} DdeEnumServices; + +typedef struct { + Conversation *currentConversations; + /* A list of conversations currently being + * processed. */ + RegisteredInterp *interpListPtr; + /* List of all interpreters registered in the + * current process. */ +} ThreadSpecificData; +static Tcl_ThreadDataKey dataKey; + +/* + * The following variables cannot be placed in thread-local storage. The Mutex + * ddeMutex guards access to the ddeInstance. + */ + +static HSZ ddeServiceGlobal = 0; +static DWORD ddeInstance; /* The application instance handle given to us + * by DdeInitialize. */ +static int ddeIsServer = 0; + +#define TCL_DDE_VERSION "1.4.6" +#define TCL_DDE_PACKAGE_NAME "dde" +#define TCL_DDE_SERVICE_NAME L"TclEval" +#define TCL_DDE_EXECUTE_RESULT L"$TCLEVAL$EXECUTE$RESULT" + +#define DDE_FLAG_ASYNC 1 +#define DDE_FLAG_BINARY 2 +#define DDE_FLAG_FORCE 4 + +TCL_DECLARE_MUTEX(ddeMutex) + +#if TCL_MAJOR_VERSION < 9 +# if TCL_UTF_MAX > 3 +# define Tcl_WCharToUtfDString(a,b,c) Tcl_WinTCharToUtf((TCHAR *)(a),(b)*sizeof(WCHAR),c) +# define Tcl_UtfToWCharDString(a,b,c) (WCHAR *)Tcl_WinUtfToTChar(a,b,c) +# else +# define Tcl_WCharToUtfDString(a,b,c) Tcl_UniCharToUtfDString((Tcl_UniChar *)(a),b,c) +# define Tcl_UtfToWCharDString(a,b,c) (WCHAR *)Tcl_UtfToUniCharDString(a,b,c) +# endif +# ifndef Tcl_Size +# define Tcl_Size int +# endif +# ifndef Tcl_ObjCmdProc2 +# define Tcl_ObjCmdProc2 Tcl_ObjCmdProc +# endif +# ifndef Tcl_CreateObjCommand2 +# define Tcl_CreateObjCommand2 Tcl_CreateObjCommand +# endif +#endif + +/* + * Declarations for functions defined in this file. + */ + +static LRESULT CALLBACK DdeClientWindowProc(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam); +static int DdeCreateClient(DdeEnumServices *es); +static BOOL CALLBACK DdeEnumWindowsCallback(HWND hwndTarget, + LPARAM lParam); +static void DdeExitProc(void *clientData); +static int DdeGetServicesList(Tcl_Interp *interp, + const WCHAR *serviceName, const WCHAR *topicName); +static HDDEDATA CALLBACK DdeServerProc(UINT uType, UINT uFmt, HCONV hConv, + HSZ ddeTopic, HSZ ddeItem, HDDEDATA hData, + DWORD dwData1, DWORD dwData2); +static LRESULT DdeServicesOnAck(HWND hwnd, WPARAM wParam, + LPARAM lParam); +static void DeleteProc(void *clientData); +static Tcl_Obj * ExecuteRemoteObject(RegisteredInterp *riPtr, + Tcl_Obj *ddeObjectPtr); +static int MakeDdeConnection(Tcl_Interp *interp, + const WCHAR *name, HCONV *ddeConvPtr); +static void SetDdeError(Tcl_Interp *interp); +static int DdeObjCmd(void *clientData, + Tcl_Interp *interp, Tcl_Size objc, + Tcl_Obj *const objv[]); + +#ifdef __cplusplus +extern "C" { +#endif +DLLEXPORT int Dde_Init(Tcl_Interp *interp); +DLLEXPORT int Dde_SafeInit(Tcl_Interp *interp); +#if TCL_MAJOR_VERSION < 9 +/* With those additional entries, "load tcldde14.dll" works without 3th argument */ +DLLEXPORT int Tcldde_Init(Tcl_Interp *interp); +DLLEXPORT int Tcldde_SafeInit(Tcl_Interp *interp); +#endif +#ifdef __cplusplus +} +#endif + +/* + *---------------------------------------------------------------------- + * + * Dde_Init -- + * + * This function initializes the dde command. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +Dde_Init( + Tcl_Interp *interp) +{ + if (!Tcl_InitStubs(interp, "8.5-", 0)) { + return TCL_ERROR; + } + + Tcl_CreateObjCommand2(interp, "dde", DdeObjCmd, NULL, NULL); + Tcl_CreateExitHandler(DdeExitProc, NULL); + return Tcl_PkgProvideEx(interp, TCL_DDE_PACKAGE_NAME, TCL_DDE_VERSION, NULL); +} +#if TCL_MAJOR_VERSION < 9 +int +Tcldde_Init( + Tcl_Interp *interp) +{ + return Dde_Init(interp); +} +#endif + +/* + *---------------------------------------------------------------------- + * + * Dde_SafeInit -- + * + * This function initializes the dde command within a safe interp + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +Dde_SafeInit( + Tcl_Interp *interp) +{ + int result = Dde_Init(interp); + if (result == TCL_OK) { + Tcl_HideCommand(interp, "dde", "dde"); + } + return result; +} +#if TCL_MAJOR_VERSION < 9 +int +Tcldde_SafeInit( + Tcl_Interp *interp) +{ + return Dde_SafeInit(interp); +} +#endif + +/* + *---------------------------------------------------------------------- + * + * Initialize -- + * + * Initialize the global DDE instance. + * + * Results: + * None. + * + * Side effects: + * Registers the DDE server proc. + * + *---------------------------------------------------------------------- + */ + +static void +Initialize(void) +{ + int nameFound = 0; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * See if the application is already registered; if so, remove its current + * name from the registry. The deletion of the command will take care of + * disposing of this entry. + */ + + if (tsdPtr->interpListPtr != NULL) { + nameFound = 1; + } + + /* + * Make sure that the DDE server is there. This is done only once, add an + * exit handler tear it down. + */ + + if (ddeInstance == 0) { + Tcl_MutexLock(&ddeMutex); + if (ddeInstance == 0) { + if (DdeInitializeW(&ddeInstance, (PFNCALLBACK)(void *)DdeServerProc, + CBF_SKIP_REGISTRATIONS | CBF_SKIP_UNREGISTRATIONS + | CBF_FAIL_POKES, 0) != DMLERR_NO_ERROR) { + ddeInstance = 0; + } + } + Tcl_MutexUnlock(&ddeMutex); + } + if ((ddeServiceGlobal == 0) && (nameFound != 0)) { + Tcl_MutexLock(&ddeMutex); + if ((ddeServiceGlobal == 0) && (nameFound != 0)) { + ddeIsServer = 1; + Tcl_CreateExitHandler(DdeExitProc, NULL); + ddeServiceGlobal = DdeCreateStringHandleW(ddeInstance, + TCL_DDE_SERVICE_NAME, CP_WINUNICODE); + DdeNameService(ddeInstance, ddeServiceGlobal, 0L, DNS_REGISTER); + } else { + ddeIsServer = 0; + } + Tcl_MutexUnlock(&ddeMutex); + } +} + +/* + *---------------------------------------------------------------------- + * + * DdeSetServerName -- + * + * This function is called to associate an ASCII name with a Dde server. + * If the interpreter has already been named, the name replaces the old + * one. + * + * Results: + * The return value is the name actually given to the interp. This will + * normally be the same as name, but if name was already in use for a Dde + * Server then a name of the form "name #2" will be chosen, with a high + * enough number to make the name unique. + * + * Side effects: + * Registration info is saved, thereby allowing the "send" command to be + * used later to invoke commands in the application. In addition, the + * "send" command is created in the application's interpreter. The + * registration will be removed automatically if the interpreter is + * deleted or the "send" command is removed. + * + *---------------------------------------------------------------------- + */ + +static const WCHAR * +DdeSetServerName( + Tcl_Interp *interp, + const WCHAR *name, /* The name that will be used to refer to the + * interpreter in later "send" commands. Must + * be globally unique. */ + int flags, /* DDE_FLAG_FORCE or 0 */ + Tcl_Obj *handlerPtr) /* Name of the optional proc/command to handle + * incoming Dde eval's */ +{ + int suffix; + RegisteredInterp *riPtr, *prevPtr; + Tcl_DString dString; + const WCHAR *actualName; + Tcl_Obj *srvListPtr = NULL, **srvPtrPtr = NULL; + Tcl_Size n, srvCount = 0, offset; + int lastSuffix, r = TCL_OK; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * See if the application is already registered; if so, remove its current + * name from the registry. The deletion of the command will take care of + * disposing of this entry. + */ + + for (riPtr = tsdPtr->interpListPtr, prevPtr = NULL; riPtr != NULL; + prevPtr = riPtr, riPtr = riPtr->nextPtr) { + if (riPtr->interp == interp) { + if (name != NULL) { + if (prevPtr == NULL) { + tsdPtr->interpListPtr = tsdPtr->interpListPtr->nextPtr; + } else { + prevPtr->nextPtr = riPtr->nextPtr; + } + break; + } else { + /* + * The name was NULL, so the caller is asking for the name of + * the current interp. + */ + + return riPtr->name; + } + } + } + + if (name == NULL) { + /* + * The name was NULL, so the caller is asking for the name of the + * current interp, but it doesn't have a name. + */ + + return L""; + } + + /* + * Get the list of currently registered Tcl interpreters by calling the + * internal implementation of the 'dde services' command. + */ + + Tcl_DStringInit(&dString); + actualName = name; + + if (!(flags & DDE_FLAG_FORCE)) { + r = DdeGetServicesList(interp, TCL_DDE_SERVICE_NAME, NULL); + if (r == TCL_OK) { + srvListPtr = Tcl_GetObjResult(interp); + } + if (r == TCL_OK) { + r = Tcl_ListObjGetElements(interp, srvListPtr, &srvCount, + &srvPtrPtr); + } + if (r != TCL_OK) { + Tcl_DStringInit(&dString); + OutputDebugStringW(Tcl_UtfToWCharDString(Tcl_GetString(Tcl_GetObjResult(interp)), -1, &dString)); + Tcl_DStringFree(&dString); + return NULL; + } + + /* + * Pick a name to use for the application. Use "name" if it's not + * already in use. Otherwise add a suffix such as " #2", trying larger + * and larger numbers until we eventually find one that is unique. + */ + + offset = lastSuffix = 0; + suffix = 1; + + while (suffix != lastSuffix) { + lastSuffix = suffix; + if (suffix > 1) { + if (suffix == 2) { + Tcl_DStringAppend(&dString, (char *)name, wcslen(name) * sizeof(WCHAR)); + Tcl_DStringAppend(&dString, (char *)L" #", 2 * sizeof(WCHAR)); + offset = Tcl_DStringLength(&dString); + Tcl_DStringSetLength(&dString, offset + sizeof(WCHAR) * TCL_INTEGER_SPACE); + actualName = (WCHAR *) Tcl_DStringValue(&dString); + } + _snwprintf((WCHAR *) (Tcl_DStringValue(&dString) + offset), + TCL_INTEGER_SPACE, L"%d", suffix); + } + + /* + * See if the name is already in use, if so increment suffix. + */ + + for (n = 0; n < srvCount; ++n) { + Tcl_Obj* namePtr; + Tcl_DString ds; + + Tcl_ListObjIndex(interp, srvPtrPtr[n], 1, &namePtr); + Tcl_DStringInit(&ds); + Tcl_UtfToWCharDString(Tcl_GetString(namePtr), -1, &ds); + if (wcscmp(actualName, (WCHAR *)Tcl_DStringValue(&ds)) == 0) { + suffix++; + Tcl_DStringFree(&ds); + break; + } + Tcl_DStringFree(&ds); + } + } + } + + /* + * We have found a unique name. Now add it to the registry. + */ + + riPtr = (RegisteredInterp *) Tcl_Alloc(sizeof(RegisteredInterp)); + riPtr->interp = interp; + riPtr->name = (WCHAR *) Tcl_Alloc((wcslen(actualName) + 1) * sizeof(WCHAR)); + riPtr->nextPtr = tsdPtr->interpListPtr; + riPtr->handlerPtr = handlerPtr; + if (riPtr->handlerPtr != NULL) { + Tcl_IncrRefCount(riPtr->handlerPtr); + } + tsdPtr->interpListPtr = riPtr; + wcscpy(riPtr->name, actualName); + + if (Tcl_IsSafe(interp)) { + Tcl_ExposeCommand(interp, "dde", "dde"); + } + + Tcl_CreateObjCommand2(interp, "dde", DdeObjCmd, + riPtr, DeleteProc); + if (Tcl_IsSafe(interp)) { + Tcl_HideCommand(interp, "dde", "dde"); + } + Tcl_DStringFree(&dString); + + /* + * Re-initialize with the new name. + */ + + Initialize(); + + return riPtr->name; +} + +/* + *---------------------------------------------------------------------- + * + * DdeGetRegistrationPtr + * + * Retrieve the registration info for an interpreter. + * + * Results: + * Returns a pointer to the registration structure or NULL + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static RegisteredInterp * +DdeGetRegistrationPtr( + Tcl_Interp *interp) +{ + RegisteredInterp *riPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + for (riPtr = tsdPtr->interpListPtr; riPtr != NULL; + riPtr = riPtr->nextPtr) { + if (riPtr->interp == interp) { + break; + } + } + return riPtr; +} + +/* + *---------------------------------------------------------------------- + * + * DeleteProc + * + * This function is called when the command "dde" is destroyed. + * + * Results: + * none + * + * Side effects: + * The interpreter given by riPtr is unregistered. + * + *---------------------------------------------------------------------- + */ + +static void +DeleteProc( + void *clientData) /* The interp we are deleting. */ +{ + RegisteredInterp *riPtr = (RegisteredInterp *) clientData; + RegisteredInterp *searchPtr, *prevPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + for (searchPtr = tsdPtr->interpListPtr, prevPtr = NULL; + (searchPtr != NULL) && (searchPtr != riPtr); + prevPtr = searchPtr, searchPtr = searchPtr->nextPtr) { + /* + * Empty loop body. + */ + } + + if (searchPtr != NULL) { + if (prevPtr == NULL) { + tsdPtr->interpListPtr = tsdPtr->interpListPtr->nextPtr; + } else { + prevPtr->nextPtr = searchPtr->nextPtr; + } + } + Tcl_Free((char *) riPtr->name); + if (riPtr->handlerPtr) { + Tcl_DecrRefCount(riPtr->handlerPtr); + } + Tcl_EventuallyFree(clientData, TCL_DYNAMIC); +} + +/* + *---------------------------------------------------------------------- + * + * ExecuteRemoteObject -- + * + * Takes the package delivered by DDE and executes it in the server's + * interpreter. + * + * Results: + * A list Tcl_Obj * that describes what happened. The first element is + * the numerical return code (TCL_ERROR, etc.). The second element is the + * result of the script. If the return result was TCL_ERROR, then the + * third element will be the value of the global "errorCode", and the + * fourth will be the value of the global "errorInfo". The return result + * will have a refCount of 0. + * + * Side effects: + * A Tcl script is run, which can cause all kinds of other things to + * happen. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Obj * +ExecuteRemoteObject( + RegisteredInterp *riPtr, /* Info about this server. */ + Tcl_Obj *ddeObjectPtr) /* The object to execute. */ +{ + Tcl_Obj *returnPackagePtr; + int result = TCL_OK; + + if ((riPtr->handlerPtr == NULL) && Tcl_IsSafe(riPtr->interp)) { + Tcl_SetObjResult(riPtr->interp, Tcl_NewStringObj("permission denied: " + "a handler procedure must be defined for use in a safe " + "interp", -1)); + Tcl_SetErrorCode(riPtr->interp, "TCL", "DDE", "SECURITY_CHECK", (char *)NULL); + result = TCL_ERROR; + } + + if (riPtr->handlerPtr != NULL) { + /* + * Add the dde request data to the handler proc list. + */ + + Tcl_Obj *cmdPtr = Tcl_DuplicateObj(riPtr->handlerPtr); + + result = Tcl_ListObjAppendElement(riPtr->interp, cmdPtr, + ddeObjectPtr); + if (result == TCL_OK) { + ddeObjectPtr = cmdPtr; + } + } + + if (result == TCL_OK) { + result = Tcl_EvalObjEx(riPtr->interp, ddeObjectPtr, TCL_EVAL_GLOBAL); + } + + returnPackagePtr = Tcl_NewListObj(0, NULL); + + Tcl_ListObjAppendElement(NULL, returnPackagePtr, + Tcl_NewIntObj(result)); + Tcl_ListObjAppendElement(NULL, returnPackagePtr, + Tcl_GetObjResult(riPtr->interp)); + + if (result == TCL_ERROR) { + Tcl_Obj *errorObjPtr = Tcl_GetVar2Ex(riPtr->interp, "errorCode", NULL, + TCL_GLOBAL_ONLY); + if (errorObjPtr) { + Tcl_ListObjAppendElement(NULL, returnPackagePtr, errorObjPtr); + } + errorObjPtr = Tcl_GetVar2Ex(riPtr->interp, "errorInfo", NULL, + TCL_GLOBAL_ONLY); + if (errorObjPtr) { + Tcl_ListObjAppendElement(NULL, returnPackagePtr, errorObjPtr); + } + } + + return returnPackagePtr; +} + +/* + *---------------------------------------------------------------------- + * + * DdeServerProc -- + * + * Handles all transactions for this server. Can handle execute, request, + * and connect protocols. Dde will call this routine when a client + * attempts to run a dde command using this server. + * + * Results: + * A DDE Handle with the result of the dde command. + * + * Side effects: + * Depending on which command is executed, arbitrary Tcl scripts can be + * run. + * + *---------------------------------------------------------------------- + */ + +static HDDEDATA CALLBACK +DdeServerProc( + UINT uType, /* The type of DDE transaction we are + * performing. */ + UINT uFmt, /* The format that data is sent or received */ + HCONV hConv, /* The conversation associated with the + * current transaction. */ + HSZ ddeTopic, HSZ ddeItem, /* String handles. Transaction-type + * dependent. */ + HDDEDATA hData, /* DDE data. Transaction-type dependent. */ + DWORD unused1, DWORD unused2) + /* Transaction-dependent data. */ +{ + Tcl_DString dString; + Tcl_Size len; + DWORD dlen; + WCHAR *utilString; + Tcl_Obj *ddeObjectPtr; + HDDEDATA ddeReturn = NULL; + RegisteredInterp *riPtr; + Conversation *convPtr, *prevConvPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + (void)unused1; + (void)unused2; + + switch(uType) { + case XTYP_CONNECT: + /* + * Dde is trying to initialize a conversation with us. Check and make + * sure we have a valid topic. + */ + + len = DdeQueryStringW(ddeInstance, ddeTopic, NULL, 0, CP_WINUNICODE); + Tcl_DStringInit(&dString); + Tcl_DStringSetLength(&dString, (len + 1) * sizeof(WCHAR) - 1); + utilString = (WCHAR *) Tcl_DStringValue(&dString); + DdeQueryStringW(ddeInstance, ddeTopic, utilString, (DWORD) len + 1, + CP_WINUNICODE); + + for (riPtr = tsdPtr->interpListPtr; riPtr != NULL; + riPtr = riPtr->nextPtr) { + if (_wcsicmp(utilString, riPtr->name) == 0) { + Tcl_DStringFree(&dString); + return (HDDEDATA) TRUE; + } + } + + Tcl_DStringFree(&dString); + return (HDDEDATA) FALSE; + + case XTYP_CONNECT_CONFIRM: + /* + * Dde has decided that we can connect, so it gives us a conversation + * handle. We need to keep track of it so we know which execution + * result to return in an XTYP_REQUEST. + */ + + len = DdeQueryStringW(ddeInstance, ddeTopic, NULL, 0, CP_WINUNICODE); + Tcl_DStringInit(&dString); + Tcl_DStringSetLength(&dString, (len + 1) * sizeof(WCHAR) - 1); + utilString = (WCHAR *) Tcl_DStringValue(&dString); + DdeQueryStringW(ddeInstance, ddeTopic, utilString, (DWORD) len + 1, + CP_WINUNICODE); + for (riPtr = tsdPtr->interpListPtr; riPtr != NULL; + riPtr = riPtr->nextPtr) { + if (_wcsicmp(riPtr->name, utilString) == 0) { + convPtr = (Conversation *) Tcl_Alloc(sizeof(Conversation)); + convPtr->nextPtr = tsdPtr->currentConversations; + convPtr->returnPackagePtr = NULL; + convPtr->hConv = hConv; + convPtr->riPtr = riPtr; + tsdPtr->currentConversations = convPtr; + break; + } + } + Tcl_DStringFree(&dString); + return (HDDEDATA) TRUE; + + case XTYP_DISCONNECT: + /* + * The client has disconnected from our server. Forget this + * conversation. + */ + + for (convPtr = tsdPtr->currentConversations, prevConvPtr = NULL; + convPtr != NULL; + prevConvPtr = convPtr, convPtr = convPtr->nextPtr) { + if (hConv == convPtr->hConv) { + if (prevConvPtr == NULL) { + tsdPtr->currentConversations = convPtr->nextPtr; + } else { + prevConvPtr->nextPtr = convPtr->nextPtr; + } + if (convPtr->returnPackagePtr != NULL) { + Tcl_DecrRefCount(convPtr->returnPackagePtr); + } + Tcl_Free((char *) convPtr); + break; + } + } + return (HDDEDATA) TRUE; + + case XTYP_REQUEST: + /* + * This could be either a request for a value of a Tcl variable, or it + * could be the send command requesting the results of the last + * execute. + */ + + if ((uFmt != CF_TEXT) && (uFmt != CF_UNICODETEXT)) { + return (HDDEDATA) FALSE; + } + + ddeReturn = (HDDEDATA) FALSE; + for (convPtr = tsdPtr->currentConversations; (convPtr != NULL) + && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) { + /* + * Empty loop body. + */ + } + + if (convPtr != NULL) { + Tcl_DString dsBuf; + char *returnString; + + len = DdeQueryStringW(ddeInstance, ddeItem, NULL, 0, CP_WINUNICODE); + Tcl_DStringInit(&dString); + Tcl_DStringInit(&dsBuf); + Tcl_DStringSetLength(&dString, (len + 1) * sizeof(WCHAR) - 1); + utilString = (WCHAR *) Tcl_DStringValue(&dString); + DdeQueryStringW(ddeInstance, ddeItem, utilString, (DWORD) len + 1, + CP_WINUNICODE); + if (_wcsicmp(utilString, TCL_DDE_EXECUTE_RESULT) == 0) { + returnString = + Tcl_GetStringFromObj(convPtr->returnPackagePtr, &len); + if (uFmt != CF_TEXT) { + Tcl_DStringInit(&dsBuf); + Tcl_UtfToWCharDString(returnString, len, &dsBuf); + returnString = Tcl_DStringValue(&dsBuf); + len = Tcl_DStringLength(&dsBuf) + sizeof(WCHAR) - 1; + } + ddeReturn = DdeCreateDataHandle(ddeInstance, (BYTE *)returnString, + (DWORD) len+1, 0, ddeItem, uFmt, 0); + } else { + if (Tcl_IsSafe(convPtr->riPtr->interp)) { + ddeReturn = NULL; + } else { + Tcl_DString ds; + Tcl_Obj *variableObjPtr; + + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString(utilString, wcslen(utilString), &ds); + variableObjPtr = Tcl_GetVar2Ex( + convPtr->riPtr->interp, Tcl_DStringValue(&ds), NULL, + TCL_GLOBAL_ONLY); + if (variableObjPtr != NULL) { + returnString = Tcl_GetStringFromObj(variableObjPtr, &len); + if (uFmt != CF_TEXT) { + Tcl_DStringInit(&dsBuf); + Tcl_UtfToWCharDString(returnString, len, &dsBuf); + returnString = Tcl_DStringValue(&dsBuf); + len = Tcl_DStringLength(&dsBuf) + sizeof(WCHAR) - 1; + } + ddeReturn = DdeCreateDataHandle(ddeInstance, + (BYTE *)returnString, (DWORD) len+1, 0, ddeItem, + uFmt, 0); + } else { + ddeReturn = NULL; + } + Tcl_DStringFree(&ds); + } + } + Tcl_DStringFree(&dsBuf); + Tcl_DStringFree(&dString); + } + return ddeReturn; + +#if !CBF_FAIL_POKES + case XTYP_POKE: + /* + * This is a poke for a Tcl variable, only implemented in + * debug/UNICODE mode. + */ + ddeReturn = DDE_FNOTPROCESSED; + + if ((uFmt != CF_TEXT) && (uFmt != CF_UNICODETEXT)) { + return ddeReturn; + } + + for (convPtr = tsdPtr->currentConversations; (convPtr != NULL) + && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) { + /* + * Empty loop body. + */ + } + + if (convPtr && !Tcl_IsSafe(convPtr->riPtr->interp)) { + Tcl_DString ds, ds2; + Tcl_Obj *variableObjPtr; + DWORD len2; + + Tcl_DStringInit(&dString); + Tcl_DStringInit(&ds2); + len = DdeQueryStringW(ddeInstance, ddeItem, NULL, 0, CP_WINUNICODE); + Tcl_DStringSetLength(&dString, (len + 1) * sizeof(WCHAR) - 1); + utilString = (WCHAR *) Tcl_DStringValue(&dString); + DdeQueryStringW(ddeInstance, ddeItem, utilString, (DWORD) len + 1, + CP_WINUNICODE); + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString(utilString, wcslen(utilString), &ds); + utilString = (WCHAR *) DdeAccessData(hData, &len2); + len = len2; + if (uFmt != CF_TEXT) { + Tcl_DStringInit(&ds2); + Tcl_WCharToUtfDString(utilString, wcslen(utilString), &ds2); + utilString = (WCHAR *) Tcl_DStringValue(&ds2); + } + variableObjPtr = Tcl_NewStringObj((char *)utilString, -1); + + Tcl_SetVar2Ex(convPtr->riPtr->interp, Tcl_DStringValue(&ds), NULL, + variableObjPtr, TCL_GLOBAL_ONLY); + + Tcl_DStringFree(&ds2); + Tcl_DStringFree(&ds); + Tcl_DStringFree(&dString); + ddeReturn = (HDDEDATA) DDE_FACK; + } + return ddeReturn; + +#endif + case XTYP_EXECUTE: { + /* + * Execute this script. The results will be saved into a list object + * which will be retrieved later. See ExecuteRemoteObject. + */ + + Tcl_Obj *returnPackagePtr; + char *string; + + for (convPtr = tsdPtr->currentConversations; (convPtr != NULL) + && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) { + /* + * Empty loop body. + */ + } + + if (convPtr == NULL) { + return (HDDEDATA) DDE_FNOTPROCESSED; + } + + utilString = (WCHAR *) DdeAccessData(hData, &dlen); + string = (char *) utilString; + if (!dlen) { + /* Empty binary array. */ + ddeObjectPtr = Tcl_NewObj(); + } else if ((dlen & 1) || utilString[(dlen>>1)-1]) { + /* Cannot be Unicode, so assume utf-8 */ + if (!string[dlen-1]) { + dlen--; + } + ddeObjectPtr = Tcl_NewStringObj(string, dlen); + } else { + /* Unicode */ + Tcl_DString dsBuf; + + Tcl_DStringInit(&dsBuf); + Tcl_WCharToUtfDString(utilString, (dlen>>1) - 1, &dsBuf); + ddeObjectPtr = Tcl_NewStringObj(Tcl_DStringValue(&dsBuf), + Tcl_DStringLength(&dsBuf)); + Tcl_DStringFree(&dsBuf); + } + Tcl_IncrRefCount(ddeObjectPtr); + DdeUnaccessData(hData); + if (convPtr->returnPackagePtr != NULL) { + Tcl_DecrRefCount(convPtr->returnPackagePtr); + } + convPtr->returnPackagePtr = NULL; + returnPackagePtr = ExecuteRemoteObject(convPtr->riPtr, ddeObjectPtr); + Tcl_IncrRefCount(returnPackagePtr); + for (convPtr = tsdPtr->currentConversations; (convPtr != NULL) + && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) { + /* + * Empty loop body. + */ + } + if (convPtr != NULL) { + convPtr->returnPackagePtr = returnPackagePtr; + } else { + Tcl_DecrRefCount(returnPackagePtr); + } + Tcl_DecrRefCount(ddeObjectPtr); + if (returnPackagePtr == NULL) { + return (HDDEDATA) DDE_FNOTPROCESSED; + } else { + return (HDDEDATA) DDE_FACK; + } + } + + case XTYP_WILDCONNECT: { + /* + * Dde wants a list of services and topics that we support. + */ + + HSZPAIR *returnPtr; + Tcl_Size i; + DWORD numItems; + + for (i = 0, riPtr = tsdPtr->interpListPtr; riPtr != NULL; + i++, riPtr = riPtr->nextPtr) { + /* + * Empty loop body. + */ + } + + if ((size_t)i >= UINT_MAX/sizeof(HSZPAIR)) { + return NULL; + } + numItems = (DWORD)i; + ddeReturn = DdeCreateDataHandle(ddeInstance, NULL, + (numItems + 1) * (DWORD)sizeof(HSZPAIR), 0, 0, 0, 0); + returnPtr = (HSZPAIR *) DdeAccessData(ddeReturn, &dlen); + len = dlen; + for (i = 0, riPtr = tsdPtr->interpListPtr; i < (Tcl_Size)numItems; + i++, riPtr = riPtr->nextPtr) { + returnPtr[i].hszSvc = DdeCreateStringHandleW(ddeInstance, + TCL_DDE_SERVICE_NAME, CP_WINUNICODE); + returnPtr[i].hszTopic = DdeCreateStringHandleW(ddeInstance, + riPtr->name, CP_WINUNICODE); + } + returnPtr[i].hszSvc = NULL; + returnPtr[i].hszTopic = NULL; + DdeUnaccessData(ddeReturn); + return ddeReturn; + } + + default: + return NULL; + } +} + +/* + *---------------------------------------------------------------------- + * + * DdeExitProc -- + * + * Gets rid of our DDE server when we go away. + * + * Results: + * None. + * + * Side effects: + * The DDE server is deleted. + * + *---------------------------------------------------------------------- + */ + +static void +DdeExitProc( + void *dummy) /* Not used. */ +{ + (void)dummy; + DdeNameService(ddeInstance, NULL, 0, DNS_UNREGISTER); + DdeUninitialize(ddeInstance); + ddeInstance = 0; +} + +/* + *---------------------------------------------------------------------- + * + * MakeDdeConnection -- + * + * This function is a utility used to connect to a DDE server when given + * a server name and a topic name. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Passes back a conversation through ddeConvPtr + * + *---------------------------------------------------------------------- + */ + +static int +MakeDdeConnection( + Tcl_Interp *interp, /* Used to report errors. */ + const WCHAR *name, /* The connection to use. */ + HCONV *ddeConvPtr) +{ + HSZ ddeTopic, ddeService; + HCONV ddeConv; + + ddeService = DdeCreateStringHandleW(ddeInstance, TCL_DDE_SERVICE_NAME, CP_WINUNICODE); + ddeTopic = DdeCreateStringHandleW(ddeInstance, name, CP_WINUNICODE); + + ddeConv = DdeConnect(ddeInstance, ddeService, ddeTopic, NULL); + DdeFreeStringHandle(ddeInstance, ddeService); + DdeFreeStringHandle(ddeInstance, ddeTopic); + + if (ddeConv == (HCONV) NULL) { + if (interp != NULL) { + Tcl_DString dString; + + Tcl_DStringInit(&dString); + Tcl_WCharToUtfDString(name, wcslen(name), &dString); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "no registered server named \"%s\"", Tcl_DStringValue(&dString))); + Tcl_DStringFree(&dString); + Tcl_SetErrorCode(interp, "TCL", "DDE", "NO_SERVER", (char *)NULL); + } + return TCL_ERROR; + } + + *ddeConvPtr = ddeConv; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * DdeGetServicesList -- + * + * This function obtains the list of DDE services. + * + * The functions between here and this function are all involved with + * handling the DDE callbacks for this. They are: DdeCreateClient, + * DdeClientWindowProc, DdeServicesOnAck, and DdeEnumWindowsCallback + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Sets the services list into the interp result. + * + *---------------------------------------------------------------------- + */ + +static int +DdeCreateClient( + DdeEnumServices *es) +{ + WNDCLASSEXW wc; + static const WCHAR *szDdeClientClassName = L"TclEval client class"; + static const WCHAR *szDdeClientWindowName = L"TclEval client window"; + + memset(&wc, 0, sizeof(wc)); + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = DdeClientWindowProc; + wc.lpszClassName = szDdeClientClassName; + wc.cbWndExtra = sizeof(DdeEnumServices *); + + /* + * Register and create the callback window. + */ + + RegisterClassExW(&wc); + es->hwnd = CreateWindowExW(0, szDdeClientClassName, szDdeClientWindowName, + WS_POPUP, 0, 0, 0, 0, NULL, NULL, NULL, (LPVOID)es); + return TCL_OK; +} + +static LRESULT CALLBACK +DdeClientWindowProc( + HWND hwnd, /* What window is the message for */ + UINT uMsg, /* The type of message received */ + WPARAM wParam, + LPARAM lParam) /* (Potentially) our local handle */ +{ + switch (uMsg) { + case WM_CREATE: { + LPCREATESTRUCT lpcs = (LPCREATESTRUCT) lParam; + DdeEnumServices *es = + (DdeEnumServices *) lpcs->lpCreateParams; + +#ifdef _WIN64 + SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) es); +#else + SetWindowLongW(hwnd, GWL_USERDATA, (LONG) es); +#endif + return (LRESULT) 0L; + } + case WM_DDE_ACK: + return DdeServicesOnAck(hwnd, wParam, lParam); + default: + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } +} + +static LRESULT +DdeServicesOnAck( + HWND hwnd, + WPARAM wParam, + LPARAM lParam) +{ + HWND hwndRemote = (HWND)wParam; + ATOM service = (ATOM)LOWORD(lParam); + ATOM topic = (ATOM)HIWORD(lParam); + DdeEnumServices *es; + WCHAR sz[255]; + Tcl_DString dString; + +#ifdef _WIN64 + es = (DdeEnumServices *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); +#else + es = (DdeEnumServices *) GetWindowLongW(hwnd, GWL_USERDATA); +#endif + + if (((es->service == (ATOM)0) || (es->service == service)) + && ((es->topic == (ATOM)0) || (es->topic == topic))) { + Tcl_Obj *matchPtr = Tcl_NewListObj(0, NULL); + Tcl_Obj *resultPtr = Tcl_GetObjResult(es->interp); + + GlobalGetAtomNameW(service, sz, 255); + Tcl_DStringInit(&dString); + Tcl_WCharToUtfDString(sz, wcslen(sz), &dString); + Tcl_ListObjAppendElement(NULL, matchPtr, Tcl_NewStringObj(Tcl_DStringValue(&dString), -1)); + Tcl_DStringFree(&dString); + GlobalGetAtomNameW(topic, sz, 255); + Tcl_DStringInit(&dString); + Tcl_WCharToUtfDString(sz, wcslen(sz), &dString); + Tcl_ListObjAppendElement(NULL, matchPtr, Tcl_NewStringObj(Tcl_DStringValue(&dString), -1)); + Tcl_DStringFree(&dString); + + /* + * Adding the hwnd as a third list element provides a unique + * identifier in the case of multiple servers with the name + * application and topic names. + */ + /* + * Needs a TIP though: + * Tcl_ListObjAppendElement(NULL, matchPtr, + * Tcl_NewLongObj((long)hwndRemote)); + */ + + if (Tcl_IsShared(resultPtr)) { + resultPtr = Tcl_DuplicateObj(resultPtr); + } + if (Tcl_ListObjAppendElement(es->interp, resultPtr, + matchPtr) == TCL_OK) { + Tcl_SetObjResult(es->interp, resultPtr); + } + } + + /* + * Tell the server we are no longer interested. + */ + + PostMessageW(hwndRemote, WM_DDE_TERMINATE, (WPARAM)hwnd, 0L); + return 0L; +} + +static BOOL CALLBACK +DdeEnumWindowsCallback( + HWND hwndTarget, + LPARAM lParam) +{ + DWORD_PTR dwResult = 0; + DdeEnumServices *es = (DdeEnumServices *) lParam; + + SendMessageTimeoutW(hwndTarget, WM_DDE_INITIATE, (WPARAM)es->hwnd, + MAKELONG(es->service, es->topic), SMTO_ABORTIFHUNG, 1000, + &dwResult); + return TRUE; +} + +static int +DdeGetServicesList( + Tcl_Interp *interp, + const WCHAR *serviceName, + const WCHAR *topicName) +{ + DdeEnumServices es; + + es.interp = interp; + es.result = TCL_OK; + es.service = (serviceName == NULL) + ? (ATOM)0 : GlobalAddAtomW(serviceName); + es.topic = (topicName == NULL) ? (ATOM)0 : GlobalAddAtomW(topicName); + + Tcl_ResetResult(interp); /* our list is to be appended to result. */ + DdeCreateClient(&es); + EnumWindows(DdeEnumWindowsCallback, (LPARAM)&es); + + if (IsWindow(es.hwnd)) { + DestroyWindow(es.hwnd); + } + if (es.service != (ATOM)0) { + GlobalDeleteAtom(es.service); + } + if (es.topic != (ATOM)0) { + GlobalDeleteAtom(es.topic); + } + return es.result; +} + +/* + *---------------------------------------------------------------------- + * + * SetDdeError -- + * + * Sets the interp result to a cogent error message describing the last + * DDE error. + * + * Results: + * None. + * + * Side effects: + * The interp's result object is changed. + * + *---------------------------------------------------------------------- + */ + +static void +SetDdeError( + Tcl_Interp *interp) /* The interp to put the message in. */ +{ + const char *errorMessage, *errorCode; + + switch (DdeGetLastError(ddeInstance)) { + case DMLERR_DATAACKTIMEOUT: + case DMLERR_EXECACKTIMEOUT: + case DMLERR_POKEACKTIMEOUT: + errorMessage = "remote interpreter did not respond"; + errorCode = "TIMEOUT"; + break; + case DMLERR_BUSY: + errorMessage = "remote server is busy"; + errorCode = "BUSY"; + break; + case DMLERR_NOTPROCESSED: + errorMessage = "remote server cannot handle this command"; + errorCode = "NOCANDO"; + break; + default: + errorMessage = "dde command failed"; + errorCode = "FAILED"; + } + + Tcl_SetObjResult(interp, Tcl_NewStringObj(errorMessage, -1)); + Tcl_SetErrorCode(interp, "TCL", "DDE", errorCode, (char *)NULL); +} + +/* + *---------------------------------------------------------------------- + * + * DdeObjCmd -- + * + * This function is invoked to process the "dde" Tcl command. See the + * user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +static int +DdeObjCmd( + void *dummy, /* Not used. */ + Tcl_Interp *interp, /* The interp we are sending from */ + Tcl_Size objc, /* Number of arguments */ + Tcl_Obj *const *objv) /* The arguments */ +{ + static const char *const ddeCommands[] = { + "servername", "execute", "poke", "request", "services", "eval", NULL}; + enum DdeSubcommands { + DDE_SERVERNAME, DDE_EXECUTE, DDE_POKE, DDE_REQUEST, DDE_SERVICES, + DDE_EVAL + }; + static const char *const ddeSrvOptions[] = { + "-force", "-handler", "--", NULL + }; + enum DdeSrvOptions { + DDE_SERVERNAME_EXACT, DDE_SERVERNAME_HANDLER, DDE_SERVERNAME_LAST, + }; + static const char *const ddeExecOptions[] = { + "-async", "-binary", NULL + }; + enum DdeExecOptions { + DDE_EXEC_ASYNC, DDE_EXEC_BINARY + }; + static const char *const ddeEvalOptions[] = { + "-async", NULL + }; + static const char *const ddeReqOptions[] = { + "-binary", NULL + }; + + int index, argIndex; + Tcl_Size length, i, firstArg = 0; + int flags = 0, result = TCL_OK; + HSZ ddeService = NULL, ddeTopic = NULL, ddeItem = NULL, ddeCookie = NULL; + HDDEDATA ddeData = NULL, ddeItemData = NULL, ddeReturn; + HCONV hConv = NULL; + const WCHAR *serviceName = NULL, *topicName = NULL; + const char *string; + DWORD ddeResult; + Tcl_Obj *objPtr, *handlerPtr = NULL; + Tcl_DString serviceBuf, topicBuf, itemBuf; + (void)dummy; + + /* + * Initialize DDE server/client + */ + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "command ?arg ...?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[1], ddeCommands, "command", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + + Tcl_DStringInit(&serviceBuf); + Tcl_DStringInit(&topicBuf); + Tcl_DStringInit(&itemBuf); + switch ((enum DdeSubcommands) index) { + case DDE_SERVERNAME: + for (i = 2; i < objc; i++) { + if (Tcl_GetIndexFromObj(interp, objv[i], ddeSrvOptions, + "option", 0, &argIndex) != TCL_OK) { + /* + * If it is the last argument, it might be a server name + * instead of a bad argument. + */ + + if (i != objc-1) { + return TCL_ERROR; + } + Tcl_ResetResult(interp); + break; + } + if (argIndex == DDE_SERVERNAME_EXACT) { + flags |= DDE_FLAG_FORCE; + } else if (argIndex == DDE_SERVERNAME_HANDLER) { + if ((objc - i) == 1) { /* return current handler */ + RegisteredInterp *riPtr = DdeGetRegistrationPtr(interp); + + if (riPtr && riPtr->handlerPtr) { + Tcl_SetObjResult(interp, riPtr->handlerPtr); + } else { + Tcl_ResetResult(interp); + } + return TCL_OK; + } + handlerPtr = objv[++i]; + } else if (argIndex == DDE_SERVERNAME_LAST) { + i++; + break; + } + } + + if ((objc - i) > 1) { + Tcl_ResetResult(interp); + Tcl_WrongNumArgs(interp, 2, objv, + "?-force? ?-handler proc? ?--? ?serverName?"); + return TCL_ERROR; + } + + firstArg = (objc == i) ? 1 : i; + break; + case DDE_EXECUTE: + if (objc == 5) { + firstArg = 2; + break; + } else if ((objc >= 6) && (objc <= 7)) { + firstArg = objc - 3; + for (i = 2; i < firstArg; i++) { + if (Tcl_GetIndexFromObj(interp, objv[i], ddeExecOptions, + "option", 0, &argIndex) != TCL_OK) { + goto wrongDdeExecuteArgs; + } + if (argIndex == DDE_EXEC_ASYNC) { + flags |= DDE_FLAG_ASYNC; + } else { + flags |= DDE_FLAG_BINARY; + } + } + break; + } + /* otherwise... */ + wrongDdeExecuteArgs: + Tcl_WrongNumArgs(interp, 2, objv, + "?-async? ?-binary? serviceName topicName value"); + return TCL_ERROR; + case DDE_POKE: + if (objc == 6) { + firstArg = 2; + break; + } else if ((objc == 7) && (Tcl_GetIndexFromObj(NULL, objv[2], + ddeReqOptions, "option", 0, &argIndex) == TCL_OK)) { + flags |= DDE_FLAG_BINARY; + firstArg = 3; + break; + } + + /* + * Otherwise... + */ + + Tcl_WrongNumArgs(interp, 2, objv, + "?-binary? serviceName topicName item value"); + return TCL_ERROR; + case DDE_REQUEST: + if (objc == 5) { + firstArg = 2; + break; + } else if ((objc == 6) && (Tcl_GetIndexFromObj(NULL, objv[2], + ddeReqOptions, "option", 0, &argIndex) == TCL_OK)) { + flags |= DDE_FLAG_BINARY; + firstArg = 3; + break; + } + + /* + * Otherwise ... + */ + + Tcl_WrongNumArgs(interp, 2, objv, + "?-binary? serviceName topicName value"); + return TCL_ERROR; + case DDE_SERVICES: + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "serviceName topicName"); + return TCL_ERROR; + } + firstArg = 2; + break; + case DDE_EVAL: + if (objc < 4) { + wrongDdeEvalArgs: + Tcl_WrongNumArgs(interp, 2, objv, "?-async? serviceName args"); + return TCL_ERROR; + } else { + firstArg = 2; + if (Tcl_GetIndexFromObj(NULL, objv[2], ddeEvalOptions, "option", + 0, &argIndex) == TCL_OK) { + if (objc < 5) { + goto wrongDdeEvalArgs; + } + flags |= DDE_FLAG_ASYNC; + firstArg++; + } + break; + } + } + + Initialize(); + + if (firstArg != 1) { + const char *src = Tcl_GetStringFromObj(objv[firstArg], &length); + + Tcl_DStringInit(&serviceBuf); + Tcl_UtfToWCharDString(src, length, &serviceBuf); + serviceName = (WCHAR *) Tcl_DStringValue(&serviceBuf); + length = Tcl_DStringLength(&serviceBuf) / sizeof(WCHAR); + } else { + length = 0; + } + + if (length == 0) { + serviceName = NULL; + } else if ((index != DDE_SERVERNAME) && (index != DDE_EVAL)) { + ddeService = DdeCreateStringHandleW(ddeInstance, serviceName, + CP_WINUNICODE); + } + + if ((index != DDE_SERVERNAME) && (index != DDE_EVAL)) { + const char *src = Tcl_GetStringFromObj(objv[firstArg + 1], &length); + + Tcl_DStringInit(&topicBuf); + topicName = Tcl_UtfToWCharDString(src, length, &topicBuf); + length = Tcl_DStringLength(&topicBuf) / sizeof(WCHAR); + if (length == 0) { + topicName = NULL; + } else { + ddeTopic = DdeCreateStringHandleW(ddeInstance, topicName, + CP_WINUNICODE); + } + } + + switch ((enum DdeSubcommands) index) { + case DDE_SERVERNAME: + serviceName = DdeSetServerName(interp, serviceName, flags, + handlerPtr); + if (serviceName != NULL) { + Tcl_DString dsBuf; + + Tcl_DStringInit(&dsBuf); + Tcl_WCharToUtfDString(serviceName, wcslen(serviceName), &dsBuf); + Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_DStringValue(&dsBuf), + Tcl_DStringLength(&dsBuf))); + Tcl_DStringFree(&dsBuf); + } else { + Tcl_ResetResult(interp); + } + break; + + case DDE_EXECUTE: { + Tcl_Size dataLength; + const void *dataString; + Tcl_DString dsBuf; + + Tcl_DStringInit(&dsBuf); + if (flags & DDE_FLAG_BINARY) { + dataString = + Tcl_GetByteArrayFromObj(objv[firstArg + 2], &dataLength); + } else { + const char *src; + + src = Tcl_GetStringFromObj(objv[firstArg + 2], &dataLength); + Tcl_DStringInit(&dsBuf); + dataString = + Tcl_UtfToWCharDString(src, dataLength, &dsBuf); + dataLength = Tcl_DStringLength(&dsBuf) + sizeof(WCHAR); + } + + if (dataLength + 1 < 2) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("cannot execute null data", -1)); + Tcl_DStringFree(&dsBuf); + Tcl_SetErrorCode(interp, "TCL", "DDE", "NULL", (char *)NULL); + result = TCL_ERROR; + break; + } + hConv = DdeConnect(ddeInstance, ddeService, ddeTopic, NULL); + DdeFreeStringHandle(ddeInstance, ddeService); + DdeFreeStringHandle(ddeInstance, ddeTopic); + + if (hConv == NULL) { + Tcl_DStringFree(&dsBuf); + SetDdeError(interp); + result = TCL_ERROR; + break; + } + + ddeData = DdeCreateDataHandle(ddeInstance, (BYTE *) dataString, + (DWORD) dataLength, 0, 0, (flags & DDE_FLAG_BINARY) ? CF_TEXT : CF_UNICODETEXT, 0); + if (ddeData != NULL) { + if (flags & DDE_FLAG_ASYNC) { + DdeClientTransaction((LPBYTE) ddeData, 0xFFFFFFFF, hConv, 0, + (flags & DDE_FLAG_BINARY) ? CF_TEXT : CF_UNICODETEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, &ddeResult); + DdeAbandonTransaction(ddeInstance, hConv, ddeResult); + } else { + ddeReturn = DdeClientTransaction((LPBYTE) ddeData, 0xFFFFFFFF, + hConv, 0, (flags & DDE_FLAG_BINARY) ? CF_TEXT : CF_UNICODETEXT, XTYP_EXECUTE, 30000, NULL); + if (ddeReturn == 0) { + SetDdeError(interp); + result = TCL_ERROR; + } + } + DdeFreeDataHandle(ddeData); + } else { + SetDdeError(interp); + result = TCL_ERROR; + } + Tcl_DStringFree(&dsBuf); + break; + } + case DDE_REQUEST: { + const WCHAR *itemString; + const char *src; + + src = Tcl_GetStringFromObj(objv[firstArg + 2], &length); + Tcl_DStringInit(&itemBuf); + itemString = Tcl_UtfToWCharDString(src, length, &itemBuf); + length = Tcl_DStringLength(&itemBuf) / sizeof(WCHAR); + + if (length == 0) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("cannot request value of null data", -1)); + Tcl_SetErrorCode(interp, "TCL", "DDE", "NULL", (char *)NULL); + result = TCL_ERROR; + goto cleanup; + } + hConv = DdeConnect(ddeInstance, ddeService, ddeTopic, NULL); + DdeFreeStringHandle(ddeInstance, ddeService); + DdeFreeStringHandle(ddeInstance, ddeTopic); + + if (hConv == NULL) { + SetDdeError(interp); + result = TCL_ERROR; + } else { + Tcl_Obj *returnObjPtr; + ddeItem = DdeCreateStringHandleW(ddeInstance, itemString, + CP_WINUNICODE); + if (ddeItem != NULL) { + ddeData = DdeClientTransaction(NULL, 0, hConv, ddeItem, + (flags & DDE_FLAG_BINARY) ? CF_TEXT : CF_UNICODETEXT, XTYP_REQUEST, 5000, NULL); + if (ddeData == NULL) { + SetDdeError(interp); + result = TCL_ERROR; + } else { + DWORD tmp; + WCHAR *dataString = (WCHAR *) DdeAccessData(ddeData, &tmp); + + if (flags & DDE_FLAG_BINARY) { + returnObjPtr = + Tcl_NewByteArrayObj((BYTE *) dataString, tmp); + } else { + Tcl_DString dsBuf; + + if ((tmp >= sizeof(WCHAR)) + && !dataString[tmp / sizeof(WCHAR) - 1]) { + tmp -= (DWORD)sizeof(WCHAR); + } + Tcl_DStringInit(&dsBuf); + Tcl_WCharToUtfDString(dataString, tmp>>1, &dsBuf); + returnObjPtr = + Tcl_NewStringObj(Tcl_DStringValue(&dsBuf), + Tcl_DStringLength(&dsBuf)); + Tcl_DStringFree(&dsBuf); + } + DdeUnaccessData(ddeData); + DdeFreeDataHandle(ddeData); + Tcl_SetObjResult(interp, returnObjPtr); + } + } else { + SetDdeError(interp); + result = TCL_ERROR; + } + } + break; + } + case DDE_POKE: { + Tcl_DString dsBuf; + const WCHAR *itemString; + BYTE *dataString; + const char *src; + + src = Tcl_GetStringFromObj(objv[firstArg + 2], &length); + Tcl_DStringInit(&itemBuf); + itemString = Tcl_UtfToWCharDString(src, length, &itemBuf); + length = Tcl_DStringLength(&itemBuf) / sizeof(WCHAR); + if (length == 0) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("cannot have a null item", -1)); + Tcl_SetErrorCode(interp, "TCL", "DDE", "NULL", (char *)NULL); + result = TCL_ERROR; + goto cleanup; + } + Tcl_DStringInit(&dsBuf); + if (flags & DDE_FLAG_BINARY) { + dataString = (BYTE *) + Tcl_GetByteArrayFromObj(objv[firstArg + 3], &length); + } else { + const char *data = + Tcl_GetStringFromObj(objv[firstArg + 3], &length); + Tcl_DStringInit(&dsBuf); + dataString = (BYTE *) + Tcl_UtfToWCharDString(data, length, &dsBuf); + length = Tcl_DStringLength(&dsBuf) + sizeof(WCHAR); + } + + hConv = DdeConnect(ddeInstance, ddeService, ddeTopic, NULL); + DdeFreeStringHandle(ddeInstance, ddeService); + DdeFreeStringHandle(ddeInstance, ddeTopic); + + if (hConv == NULL) { + SetDdeError(interp); + result = TCL_ERROR; + } else { + ddeItem = DdeCreateStringHandleW(ddeInstance, itemString, + CP_WINUNICODE); + if (ddeItem != NULL) { + ddeData = DdeClientTransaction(dataString, (DWORD) length, + hConv, ddeItem, (flags & DDE_FLAG_BINARY) ? CF_TEXT : CF_UNICODETEXT, XTYP_POKE, 5000, NULL); + if (ddeData == NULL) { + SetDdeError(interp); + result = TCL_ERROR; + } + } else { + SetDdeError(interp); + result = TCL_ERROR; + } + } + Tcl_DStringFree(&dsBuf); + break; + } + + case DDE_SERVICES: + result = DdeGetServicesList(interp, serviceName, topicName); + break; + + case DDE_EVAL: { + RegisteredInterp *riPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (serviceName == NULL) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("invalid service name \"\"", -1)); + Tcl_SetErrorCode(interp, "TCL", "DDE", "NO_SERVER", (char *)NULL); + result = TCL_ERROR; + goto cleanup; + } + + objc -= firstArg + 1; + objv += firstArg + 1; + + /* + * See if the target interpreter is local. If so, execute the command + * directly without going through the DDE server. Don't exchange + * objects between interps. The target interp could compile an object, + * producing a bytecode structure that refers to other objects owned + * by the target interp. If the target interp is then deleted, the + * bytecode structure would be referring to deallocated objects. + */ + + for (riPtr = tsdPtr->interpListPtr; riPtr != NULL; + riPtr = riPtr->nextPtr) { + if (_wcsicmp(serviceName, riPtr->name) == 0) { + break; + } + } + + if (riPtr != NULL) { + Tcl_Interp *sendInterp; + + /* + * This command is to a local interp. No need to go through the + * server. + */ + + Tcl_Preserve(riPtr); + sendInterp = riPtr->interp; + Tcl_Preserve(sendInterp); + + /* + * Don't exchange objects between interps. The target interp would + * compile an object, producing a bytecode structure that refers + * to other objects owned by the target interp. If the target + * interp is then deleted, the bytecode structure would be + * referring to deallocated objects. + */ + + if (Tcl_IsSafe(riPtr->interp) && (riPtr->handlerPtr == NULL)) { + Tcl_SetObjResult(riPtr->interp, Tcl_NewStringObj( + "permission denied: a handler procedure must be" + " defined for use in a safe interp", -1)); + Tcl_SetErrorCode(interp, "TCL", "DDE", "SECURITY_CHECK", + (char *)NULL); + result = TCL_ERROR; + } + + if (result == TCL_OK) { + if (objc == 1) { + objPtr = objv[0]; + } else { + objPtr = Tcl_ConcatObj(objc, objv); + } + if (riPtr->handlerPtr != NULL) { + /* add the dde request data to the handler proc list */ + /* + *result = Tcl_ListObjReplace(sendInterp, objPtr, 0, 0, 1, + * &(riPtr->handlerPtr)); + */ + Tcl_Obj *cmdPtr = Tcl_DuplicateObj(riPtr->handlerPtr); + result = Tcl_ListObjAppendElement(sendInterp, cmdPtr, + objPtr); + if (result == TCL_OK) { + objPtr = cmdPtr; + } + } + } + if (result == TCL_OK) { + Tcl_IncrRefCount(objPtr); + result = Tcl_EvalObjEx(sendInterp, objPtr, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(objPtr); + } + if (interp != sendInterp) { + if (result == TCL_ERROR) { + /* + * An error occurred, so transfer error information from + * the destination interpreter back to our interpreter. + */ + + Tcl_ResetResult(interp); + objPtr = Tcl_GetVar2Ex(sendInterp, "errorInfo", NULL, + TCL_GLOBAL_ONLY); + if (objPtr) { + Tcl_AppendObjToErrorInfo(interp, objPtr); + } + + objPtr = Tcl_GetVar2Ex(sendInterp, "errorCode", NULL, + TCL_GLOBAL_ONLY); + if (objPtr) { + Tcl_SetObjErrorCode(interp, objPtr); + } + } + Tcl_SetObjResult(interp, Tcl_GetObjResult(sendInterp)); + } + Tcl_Release(riPtr); + Tcl_Release(sendInterp); + } else { + Tcl_DString dsBuf; + + /* + * This is a non-local request. Send the script to the server and + * poll it for a result. + */ + + if (MakeDdeConnection(interp, serviceName, &hConv) != TCL_OK) { + invalidServerResponse: + Tcl_SetObjResult(interp, + Tcl_NewStringObj("invalid data returned from server", -1)); + Tcl_SetErrorCode(interp, "TCL", "DDE", "BAD_RESPONSE", (char *)NULL); + result = TCL_ERROR; + goto cleanup; + } + + objPtr = Tcl_ConcatObj(objc, objv); + string = Tcl_GetStringFromObj(objPtr, &length); + Tcl_DStringInit(&dsBuf); + Tcl_UtfToWCharDString(string, length, &dsBuf); + string = Tcl_DStringValue(&dsBuf); + length = Tcl_DStringLength(&dsBuf) + sizeof(WCHAR); + ddeItemData = DdeCreateDataHandle(ddeInstance, (BYTE *) string, + (DWORD) length, 0, 0, CF_UNICODETEXT, 0); + Tcl_DStringFree(&dsBuf); + + if (flags & DDE_FLAG_ASYNC) { + ddeData = DdeClientTransaction((LPBYTE) ddeItemData, + 0xFFFFFFFF, hConv, 0, + CF_UNICODETEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, &ddeResult); + DdeAbandonTransaction(ddeInstance, hConv, ddeResult); + } else { + ddeData = DdeClientTransaction((LPBYTE) ddeItemData, + 0xFFFFFFFF, hConv, 0, + CF_UNICODETEXT, XTYP_EXECUTE, 30000, NULL); + if (ddeData != 0) { + ddeCookie = DdeCreateStringHandleW(ddeInstance, + TCL_DDE_EXECUTE_RESULT, CP_WINUNICODE); + ddeData = DdeClientTransaction(NULL, 0, hConv, ddeCookie, + CF_UNICODETEXT, XTYP_REQUEST, 30000, NULL); + } + } + + Tcl_DecrRefCount(objPtr); + + if (ddeData == 0) { + SetDdeError(interp); + result = TCL_ERROR; + goto cleanup; + } + + if (!(flags & DDE_FLAG_ASYNC)) { + Tcl_Obj *resultPtr; + WCHAR *ddeDataString; + + /* + * The return handle has a two or four element list in it. The + * first element is the return code (TCL_OK, TCL_ERROR, etc.). + * The second is the result of the script. If the return code + * is TCL_ERROR, then the third element is the value of the + * variable "errorCode", and the fourth is the value of the + * variable "errorInfo". + */ + + length = DdeGetData(ddeData, NULL, 0, 0); + ddeDataString = (WCHAR *) Tcl_Alloc(length); + DdeGetData(ddeData, (BYTE *) ddeDataString, (DWORD) length, 0); + if (length > (Tcl_Size)sizeof(WCHAR)) { + length -= sizeof(WCHAR); + } + Tcl_DStringInit(&dsBuf); + Tcl_WCharToUtfDString(ddeDataString, length>>1, &dsBuf); + resultPtr = Tcl_NewStringObj(Tcl_DStringValue(&dsBuf), + Tcl_DStringLength(&dsBuf)); + Tcl_DStringFree(&dsBuf); + Tcl_Free((char *) ddeDataString); + + if (Tcl_ListObjIndex(NULL, resultPtr, 0, &objPtr) != TCL_OK) { + Tcl_DecrRefCount(resultPtr); + goto invalidServerResponse; + } + if (Tcl_GetIntFromObj(NULL, objPtr, &result) != TCL_OK) { + Tcl_DecrRefCount(resultPtr); + goto invalidServerResponse; + } + if (result == TCL_ERROR) { + Tcl_ResetResult(interp); + + if (Tcl_ListObjIndex(NULL, resultPtr, 3, + &objPtr) != TCL_OK) { + Tcl_DecrRefCount(resultPtr); + goto invalidServerResponse; + } + Tcl_AppendObjToErrorInfo(interp, objPtr); + + Tcl_ListObjIndex(NULL, resultPtr, 2, &objPtr); + Tcl_SetObjErrorCode(interp, objPtr); + } + if (Tcl_ListObjIndex(NULL, resultPtr, 1, &objPtr) != TCL_OK) { + Tcl_DecrRefCount(resultPtr); + goto invalidServerResponse; + } + Tcl_SetObjResult(interp, objPtr); + Tcl_DecrRefCount(resultPtr); + } + } + } + } + + cleanup: + if (ddeCookie != NULL) { + DdeFreeStringHandle(ddeInstance, ddeCookie); + } + if (ddeItem != NULL) { + DdeFreeStringHandle(ddeInstance, ddeItem); + } + if (ddeItemData != NULL) { + DdeFreeDataHandle(ddeItemData); + } + if (ddeData != NULL) { + DdeFreeDataHandle(ddeData); + } + if (hConv != NULL) { + DdeDisconnect(hConv); + } + Tcl_DStringFree(&itemBuf); + Tcl_DStringFree(&topicBuf); + Tcl_DStringFree(&serviceBuf); + return result; +} + +/* + * Local variables: + * mode: c + * indent-tabs-mode: t + * tab-width: 8 + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclWinError.c b/vendor/tcl/win/tclWinError.c new file mode 100644 index 00000000..fb1c04c0 --- /dev/null +++ b/vendor/tcl/win/tclWinError.c @@ -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: + */ diff --git a/vendor/tcl/win/tclWinFCmd.c b/vendor/tcl/win/tclWinFCmd.c new file mode 100644 index 00000000..28e10c40 --- /dev/null +++ b/vendor/tcl/win/tclWinFCmd.c @@ -0,0 +1,2104 @@ +/* + * tclWinFCmd.c + * + * This file implements the Windows specific portion of file manipulation + * subcommands of the "file" command. + * + * Copyright © 1996-1998 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 + +/* + * The following constants specify the type of callback when + * TraverseWinTree() calls the traverseProc() + */ +enum TclTraverseWinTreeTypes { + DOTREE_PRED = 1, /* pre-order directory */ + DOTREE_POSTD = 2, /* post-order directory */ + DOTREE_F = 3, /* regular file */ + DOTREE_LINK = 4 /* symbolic link */ +}; + +/* + * Callbacks for file attributes code. + */ + +static int GetWinFileAttributes(Tcl_Interp *interp, int objIndex, + Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); +static int GetWinFileLongName(Tcl_Interp *interp, int objIndex, + Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); +static int GetWinFileShortName(Tcl_Interp *interp, int objIndex, + Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr); +static int SetWinFileAttributes(Tcl_Interp *interp, int objIndex, + Tcl_Obj *fileName, Tcl_Obj *attributePtr); +static int CannotSetAttribute(Tcl_Interp *interp, int objIndex, + Tcl_Obj *fileName, Tcl_Obj *attributePtr); + +/* + * Constants and variables necessary for file attributes subcommand. + */ + +enum { + WIN_ARCHIVE_ATTRIBUTE, + WIN_HIDDEN_ATTRIBUTE, + WIN_LONGNAME_ATTRIBUTE, + WIN_READONLY_ATTRIBUTE, + WIN_SHORTNAME_ATTRIBUTE, + WIN_SYSTEM_ATTRIBUTE +}; + +static const int attributeArray[] = { + FILE_ATTRIBUTE_ARCHIVE, // -archive + FILE_ATTRIBUTE_HIDDEN, // -hidden + 0, // -longname + FILE_ATTRIBUTE_READONLY, // -readonly + 0, // -shortname + FILE_ATTRIBUTE_SYSTEM // -system +}; + +const char *const tclpFileAttrStrings[] = { + "-archive", + "-hidden", + "-longname", + "-readonly", + "-shortname", + "-system", + NULL +}; + +const TclFileAttrProcs tclpFileAttrProcs[] = { + {GetWinFileAttributes, SetWinFileAttributes}, // -archive + {GetWinFileAttributes, SetWinFileAttributes}, // -hidden + {GetWinFileLongName, CannotSetAttribute}, // -longname + {GetWinFileAttributes, SetWinFileAttributes}, // -readonly + {GetWinFileShortName, CannotSetAttribute}, // -shortname + {GetWinFileAttributes, SetWinFileAttributes} // -system +}; + +/* + * Prototype for the TraverseWinTree callback function. + */ + +typedef int (TraversalProc)(const WCHAR *srcPtr, const WCHAR *dstPtr, + int type, Tcl_DString *errorPtr); + +/* + * Declarations for local functions defined in this file: + */ + +static void StatError(Tcl_Interp *interp, Tcl_Obj *fileName); +static int ConvertFileNameFormat(Tcl_Interp *interp, + int objIndex, Tcl_Obj *fileName, int longShort, + Tcl_Obj **attributePtrPtr); +static int DoCopyFile(const WCHAR *srcPtr, const WCHAR *dstPtr); +static int DoCreateDirectory(const WCHAR *pathPtr); +static int DoRemoveJustDirectory(const WCHAR *nativeSrc, + int ignoreError, Tcl_DString *errorPtr); +static int DoRemoveDirectory(Tcl_DString *pathPtr, int recursive, + Tcl_DString *errorPtr); +static int DoRenameFile(const WCHAR *nativeSrc, + const WCHAR *dstPtr); +static int TraversalCopy(const WCHAR *srcPtr, const WCHAR *dstPtr, + int type, Tcl_DString *errorPtr); +static int TraversalDelete(const WCHAR *srcPtr, + const WCHAR *dstPtr, int type, + Tcl_DString *errorPtr); +static int TraverseWinTree(TraversalProc *traverseProc, + Tcl_DString *sourcePtr, Tcl_DString *dstPtr, + Tcl_DString *errorPtr); + +/* + *--------------------------------------------------------------------------- + * + * TclpObjRenameFile, DoRenameFile -- + * + * Changes the name of an existing file or directory, from src to dst. + * If src and dst refer to the same file or directory, does nothing and + * returns success. Otherwise if dst already exists, it will be deleted + * and replaced by src subject to the following conditions: + * If src is a directory, dst may be an empty directory. + * If src is a file, dst may be a file. + * In any other situation where dst already exists, the rename will fail. + * + * Results: + * If the file or directory was successfully renamed, returns TCL_OK. + * Otherwise the return value is TCL_ERROR and errno is set to indicate + * the error. Some possible values for errno are: + * + * ENAMETOOLONG: src or dst names are too long. + * EACCES: src or dst parent directory can't be read and/or written. + * EEXIST: dst is a non-empty directory. + * EINVAL: src is a root directory or dst is a subdirectory of src. + * EISDIR: dst is a directory, but src is not. + * ENOENT: src doesn't exist. src or dst is "". + * ENOTDIR: src is a directory, but dst is not. + * EXDEV: src and dst are on different filesystems. + * + * EACCES: exists an open file already referring to src or dst. + * EACCES: src or dst specify the current working directory (NT). + * EACCES: src specifies a char device (nul:, com1:, etc.) + * EEXIST: dst specifies a char device (nul:, com1:, etc.) (NT) + * EACCES: dst specifies a char device (nul:, com1:, etc.) (95) + * + * Side effects: + * The implementation supports cross-filesystem renames of files, but the + * caller should be prepared to emulate cross-filesystem renames of + * directories if errno is EXDEV. + * + *--------------------------------------------------------------------------- + */ + +int +TclpObjRenameFile( + Tcl_Obj *srcPathPtr, + Tcl_Obj *destPathPtr) +{ + return DoRenameFile((const WCHAR *)Tcl_FSGetNativePath(srcPathPtr), + (const WCHAR *)Tcl_FSGetNativePath(destPathPtr)); +} + +static int +DoRenameFile( + const WCHAR *nativeSrc, /* Pathname of file or dir to be renamed + * (native). */ + const WCHAR *nativeDst) /* New pathname for file or directory + * (native). */ +{ +#if defined(HAVE_NO_SEH) && !defined(_WIN64) + TCLEXCEPTION_REGISTRATION registration; +#endif + DWORD srcAttr, dstAttr; + int retval = -1; + + /* + * The MoveFile API acts differently under Win95/98 and NT WRT NULL and + * "". Avoid passing these values. + */ + + if (nativeSrc == NULL || nativeSrc[0] == '\0' || + nativeDst == NULL || nativeDst[0] == '\0') { + Tcl_SetErrno(ENOENT); + return TCL_ERROR; + } + + /* + * The MoveFile API would throw an exception under NT if one of the + * arguments is a char block device. + */ + +#if defined(HAVE_NO_SEH) && !defined(_WIN64) + /* + * Don't have SEH available, do things the hard way. Note that this needs + * to be one block of asm, to avoid stack imbalance; also, it is illegal + * for one asm block to contain a jump to another. + */ + + __asm__ __volatile__ ( + /* + * Pick up params before messing with the stack. + */ + + "movl %[nativeDst], %%ebx" "\n\t" + "movl %[nativeSrc], %%ecx" "\n\t" + + /* + * Construct an TCLEXCEPTION_REGISTRATION to protect the call to + * MoveFile. + */ + + "leal %[registration], %%edx" "\n\t" + "movl %%fs:0, %%eax" "\n\t" + "movl %%eax, 0x0(%%edx)" "\n\t" /* link */ + "leal 1f, %%eax" "\n\t" + "movl %%eax, 0x4(%%edx)" "\n\t" /* handler */ + "movl %%ebp, 0x8(%%edx)" "\n\t" /* ebp */ + "movl %%esp, 0xC(%%edx)" "\n\t" /* esp */ + "movl $0, 0x10(%%edx)" "\n\t" /* status */ + + /* + * Link the TCLEXCEPTION_REGISTRATION on the chain. + */ + + "movl %%edx, %%fs:0" "\n\t" + + /* + * Call MoveFileW(nativeSrc, nativeDst) + */ + + "pushl %%ebx" "\n\t" + "pushl %%ecx" "\n\t" + "movl %[moveFileW], %%eax" "\n\t" + "call *%%eax" "\n\t" + + /* + * Come here on normal exit. Recover the TCLEXCEPTION_REGISTRATION and + * put the status return from MoveFile into it. + */ + + "movl %%fs:0, %%edx" "\n\t" + "movl %%eax, 0x10(%%edx)" "\n\t" + "jmp 2f" "\n" + + /* + * Come here on an exception. Recover the TCLEXCEPTION_REGISTRATION + */ + + "1:" "\t" + "movl %%fs:0, %%edx" "\n\t" + "movl 0x8(%%edx), %%edx" "\n\t" + + /* + * Come here however we exited. Restore context from the + * TCLEXCEPTION_REGISTRATION in case the stack is unbalanced. + */ + + "2:" "\t" + "movl 0xC(%%edx), %%esp" "\n\t" + "movl 0x8(%%edx), %%ebp" "\n\t" + "movl 0x0(%%edx), %%eax" "\n\t" + "movl %%eax, %%fs:0" "\n\t" + + : + /* No outputs */ + : + [registration] "m" (registration), + [nativeDst] "m" (nativeDst), + [nativeSrc] "m" (nativeSrc), + [moveFileW] "r" (MoveFileW) + : + "%eax", "%ebx", "%ecx", "%edx", "memory" + ); + if (registration.status != FALSE) { + retval = TCL_OK; + } +#else +#ifndef HAVE_NO_SEH + __try { +#endif + if (MoveFileW(nativeSrc, nativeDst) != FALSE) { + retval = TCL_OK; + } +#ifndef HAVE_NO_SEH + } __except (EXCEPTION_EXECUTE_HANDLER) {} +#endif +#endif + + if (retval != -1) { + return retval; + } + + Tcl_WinConvertError(GetLastError()); + + srcAttr = GetFileAttributesW(nativeSrc); + dstAttr = GetFileAttributesW(nativeDst); + if (srcAttr == 0xFFFFFFFF) { + if (GetFullPathNameW(nativeSrc, 0, NULL, + NULL) >= MAX_PATH) { + errno = ENAMETOOLONG; + return TCL_ERROR; + } + srcAttr = 0; + } + if (dstAttr == 0xFFFFFFFF) { + if (GetFullPathNameW(nativeDst, 0, NULL, + NULL) >= MAX_PATH) { + errno = ENAMETOOLONG; + return TCL_ERROR; + } + dstAttr = 0; + } + + if (errno == EBADF) { + errno = EACCES; + return TCL_ERROR; + } + if (errno == EACCES) { + decode: + if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { + WCHAR *nativeSrcRest, *nativeDstRest; + const char **srcArgv, **dstArgv; + size_t size; + Tcl_Size srcArgc, dstArgc; + WCHAR nativeSrcPath[MAX_PATH]; + WCHAR nativeDstPath[MAX_PATH]; + Tcl_DString srcString, dstString; + const char *src, *dst; + + size = GetFullPathNameW(nativeSrc, MAX_PATH, + nativeSrcPath, &nativeSrcRest); + if ((size == 0) || (size > MAX_PATH)) { + return TCL_ERROR; + } + size = GetFullPathNameW(nativeDst, MAX_PATH, + nativeDstPath, &nativeDstRest); + if ((size == 0) || (size > MAX_PATH)) { + return TCL_ERROR; + } + CharLowerW(nativeSrcPath); + CharLowerW(nativeDstPath); + + Tcl_DStringInit(&srcString); + Tcl_DStringInit(&dstString); + src = Tcl_WCharToUtfDString(nativeSrcPath, TCL_INDEX_NONE, &srcString); + dst = Tcl_WCharToUtfDString(nativeDstPath, TCL_INDEX_NONE, &dstString); + + /* + * Check whether the destination path is actually inside the + * source path. This is true if the prefix matches, and the next + * character is either end-of-string or a directory separator + */ + + if ((strncmp(src, dst, Tcl_DStringLength(&srcString))==0) + && (dst[Tcl_DStringLength(&srcString)] == '\\' + || dst[Tcl_DStringLength(&srcString)] == '/' + || dst[Tcl_DStringLength(&srcString)] == '\0')) { + /* + * Trying to move a directory into itself. + */ + + errno = EINVAL; + Tcl_DStringFree(&srcString); + Tcl_DStringFree(&dstString); + return TCL_ERROR; + } + Tcl_SplitPath(src, &srcArgc, &srcArgv); + Tcl_SplitPath(dst, &dstArgc, &dstArgv); + Tcl_DStringFree(&srcString); + Tcl_DStringFree(&dstString); + + if (srcArgc == 1) { + /* + * They are trying to move a root directory. Whether or not it + * is across filesystems, this cannot be done. + */ + + Tcl_SetErrno(EINVAL); + } else if ((srcArgc > 0) && (dstArgc > 0) && + (strcmp(srcArgv[0], dstArgv[0]) != 0)) { + /* + * If src is a directory and dst filesystem != src filesystem, + * errno should be EXDEV. It is very important to get this + * behavior, so that the caller can respond to a cross + * filesystem rename by simulating it with copy and delete. + * The MoveFile system call already handles the case of moving + * a file between filesystems. + */ + + Tcl_SetErrno(EXDEV); + } + + Tcl_Free((void *)srcArgv); + Tcl_Free((void *)dstArgv); + } + + /* + * Other types of access failure is that dst is a read-only + * filesystem, that an open file referred to src or dest, or that src + * or dest specified the current working directory on the current + * filesystem. EACCES is returned for those cases. + */ + + } else if (Tcl_GetErrno() == EEXIST) { + /* + * Reports EEXIST any time the target already exists. If it makes + * sense, remove the old file and try renaming again. + */ + + if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { + if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Overwrite empty dst directory with src directory. The + * following call will remove an empty directory. If it fails, + * it's because it wasn't empty. + */ + + if (DoRemoveJustDirectory(nativeDst, 0, NULL) == TCL_OK) { + /* + * Now that that empty directory is gone, we can try + * renaming again. If that fails, we'll put this empty + * directory back, for completeness. + */ + + if (MoveFileW(nativeSrc, nativeDst) != FALSE) { + return TCL_OK; + } + + /* + * Some new error has occurred. Don't know what it could + * be, but report this one. + */ + + Tcl_WinConvertError(GetLastError()); + CreateDirectoryW(nativeDst, NULL); + SetFileAttributesW(nativeDst, dstAttr); + if (Tcl_GetErrno() == EACCES) { + /* + * Decode the EACCES to a more meaningful error. + */ + + goto decode; + } + } + } else { /* (dstAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ + Tcl_SetErrno(ENOTDIR); + } + } else { /* (srcAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ + if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { + Tcl_SetErrno(EISDIR); + } else { + /* + * Overwrite existing file by: + * + * 1. Rename existing file to temp name. + * 2. Rename old file to new name. + * 3. If success, delete temp file. If failure, put temp file + * back to old name. + */ + + WCHAR *nativeRest, *nativeTmp, *nativePrefix; + int result, size; + WCHAR tempBuf[MAX_PATH]; + + size = GetFullPathNameW(nativeDst, MAX_PATH, + tempBuf, &nativeRest); + if ((size == 0) || (size > MAX_PATH) || (nativeRest == NULL)) { + return TCL_ERROR; + } + nativeTmp = (WCHAR *) tempBuf; + nativeRest[0] = '\0'; + + result = TCL_ERROR; + nativePrefix = (WCHAR *)L"tclr"; + if (GetTempFileNameW(nativeTmp, nativePrefix, + 0, tempBuf) != 0) { + /* + * Strictly speaking, need the following DeleteFile and + * MoveFile to be joined as an atomic operation so no + * other app comes along in the meantime and creates the + * same temp file. + */ + + nativeTmp = tempBuf; + DeleteFileW(nativeTmp); + if (MoveFileW(nativeDst, nativeTmp) != FALSE) { + if (MoveFileW(nativeSrc, nativeDst) != FALSE) { + SetFileAttributesW(nativeTmp, FILE_ATTRIBUTE_NORMAL); + DeleteFileW(nativeTmp); + return TCL_OK; + } else { + DeleteFileW(nativeDst); + MoveFileW(nativeTmp, nativeDst); + } + } + + /* + * Can't backup dst file or move src file. Return that + * error. Could happen if an open file refers to dst. + */ + + Tcl_WinConvertError(GetLastError()); + if (Tcl_GetErrno() == EACCES) { + /* + * Decode the EACCES to a more meaningful error. + */ + + goto decode; + } + } + return result; + } + } + } + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpObjCopyFile, DoCopyFile -- + * + * Copy a single file (not a directory). If dst already exists and is not + * a directory, it is removed. + * + * Results: + * If the file was successfully copied, returns TCL_OK. Otherwise the + * return value is TCL_ERROR and errno is set to indicate the error. + * Some possible values for errno are: + * + * EACCES: src or dst parent directory can't be read and/or written. + * EISDIR: src or dst is a directory. + * ENOENT: src doesn't exist. src or dst is "". + * + * EACCES: exists an open file already referring to dst (95). + * EACCES: src specifies a char device (nul:, com1:, etc.) (NT) + * ENOENT: src specifies a char device (nul:, com1:, etc.) (95) + * + * Side effects: + * It is not an error to copy to a char device. + * + *--------------------------------------------------------------------------- + */ + +int +TclpObjCopyFile( + Tcl_Obj *srcPathPtr, + Tcl_Obj *destPathPtr) +{ + return DoCopyFile((const WCHAR *)Tcl_FSGetNativePath(srcPathPtr), + (const WCHAR *)Tcl_FSGetNativePath(destPathPtr)); +} + +static int +DoCopyFile( + const WCHAR *nativeSrc, /* Pathname of file to be copied (native). */ + const WCHAR *nativeDst) /* Pathname of file to copy to (native). */ +{ +#if defined(HAVE_NO_SEH) && !defined(_WIN64) + TCLEXCEPTION_REGISTRATION registration; +#endif + int retval = -1; + + /* + * The CopyFile API acts differently under Win95/98 and NT WRT NULL and + * "". Avoid passing these values. + */ + + if (nativeSrc == NULL || nativeSrc[0] == '\0' || + nativeDst == NULL || nativeDst[0] == '\0') { + Tcl_SetErrno(ENOENT); + return TCL_ERROR; + } + + /* + * The CopyFile API would throw an exception under NT if one of the + * arguments is a char block device. + */ + +#if defined(HAVE_NO_SEH) && !defined(_WIN64) + /* + * Don't have SEH available, do things the hard way. Note that this needs + * to be one block of asm, to avoid stack imbalance; also, it is illegal + * for one asm block to contain a jump to another. + */ + + __asm__ __volatile__ ( + + /* + * Pick up parameters before messing with the stack + */ + + "movl %[nativeDst], %%ebx" "\n\t" + "movl %[nativeSrc], %%ecx" "\n\t" + + /* + * Construct an TCLEXCEPTION_REGISTRATION to protect the call to + * CopyFile. + */ + + "leal %[registration], %%edx" "\n\t" + "movl %%fs:0, %%eax" "\n\t" + "movl %%eax, 0x0(%%edx)" "\n\t" /* link */ + "leal 1f, %%eax" "\n\t" + "movl %%eax, 0x4(%%edx)" "\n\t" /* handler */ + "movl %%ebp, 0x8(%%edx)" "\n\t" /* ebp */ + "movl %%esp, 0xC(%%edx)" "\n\t" /* esp */ + "movl $0, 0x10(%%edx)" "\n\t" /* status */ + + /* + * Link the TCLEXCEPTION_REGISTRATION on the chain. + */ + + "movl %%edx, %%fs:0" "\n\t" + + /* + * Call CopyFileW(nativeSrc, nativeDst, 0) + */ + + "movl %[copyFileW], %%eax" "\n\t" + "pushl $0" "\n\t" + "pushl %%ebx" "\n\t" + "pushl %%ecx" "\n\t" + "call *%%eax" "\n\t" + + /* + * Come here on normal exit. Recover the TCLEXCEPTION_REGISTRATION and + * put the status return from CopyFile into it. + */ + + "movl %%fs:0, %%edx" "\n\t" + "movl %%eax, 0x10(%%edx)" "\n\t" + "jmp 2f" "\n" + + /* + * Come here on an exception. Recover the TCLEXCEPTION_REGISTRATION + */ + + "1:" "\t" + "movl %%fs:0, %%edx" "\n\t" + "movl 0x8(%%edx), %%edx" "\n\t" + + /* + * Come here however we exited. Restore context from the + * TCLEXCEPTION_REGISTRATION in case the stack is unbalanced. + */ + + "2:" "\t" + "movl 0xC(%%edx), %%esp" "\n\t" + "movl 0x8(%%edx), %%ebp" "\n\t" + "movl 0x0(%%edx), %%eax" "\n\t" + "movl %%eax, %%fs:0" "\n\t" + + : + /* No outputs */ + : + [registration] "m" (registration), + [nativeDst] "m" (nativeDst), + [nativeSrc] "m" (nativeSrc), + [copyFileW] "r" (CopyFileW) + : + "%eax", "%ebx", "%ecx", "%edx", "memory" + ); + if (registration.status != FALSE) { + retval = TCL_OK; + } +#else +#ifndef HAVE_NO_SEH + __try { +#endif + if (CopyFileW(nativeSrc, nativeDst, 0) != FALSE) { + retval = TCL_OK; + } +#ifndef HAVE_NO_SEH + } __except (EXCEPTION_EXECUTE_HANDLER) {} +#endif +#endif + + if (retval != -1) { + return retval; + } + + Tcl_WinConvertError(GetLastError()); + if (Tcl_GetErrno() == EBADF) { + Tcl_SetErrno(EACCES); + return TCL_ERROR; + } + if (Tcl_GetErrno() == EACCES) { + DWORD srcAttr, dstAttr; + + srcAttr = GetFileAttributesW(nativeSrc); + dstAttr = GetFileAttributesW(nativeDst); + if (srcAttr != 0xFFFFFFFF) { + if (dstAttr == 0xFFFFFFFF) { + dstAttr = 0; + } + if ((srcAttr & FILE_ATTRIBUTE_DIRECTORY) || + (dstAttr & FILE_ATTRIBUTE_DIRECTORY)) { + if (srcAttr & FILE_ATTRIBUTE_REPARSE_POINT) { + /* Source is a symbolic link -- copy it */ + if (TclWinSymLinkCopyDirectory(nativeSrc, nativeDst)==0) { + return TCL_OK; + } + } + Tcl_SetErrno(EISDIR); + } + if (dstAttr & FILE_ATTRIBUTE_READONLY) { + SetFileAttributesW(nativeDst, + dstAttr & ~((DWORD)FILE_ATTRIBUTE_READONLY)); + if (CopyFileW(nativeSrc, nativeDst, 0) != FALSE) { + return TCL_OK; + } + + /* + * Still can't copy onto dst. Return that error, and restore + * attributes of dst. + */ + + Tcl_WinConvertError(GetLastError()); + SetFileAttributesW(nativeDst, dstAttr); + } + } + } + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpObjDeleteFile, TclpDeleteFile -- + * + * Removes a single file (not a directory). + * + * Results: + * If the file was successfully deleted, returns TCL_OK. Otherwise the + * return value is TCL_ERROR and errno is set to indicate the error. + * Some possible values for errno are: + * + * EACCES: a parent directory can't be read and/or written. + * EISDIR: path is a directory. + * ENOENT: path doesn't exist or is "". + * + * EACCES: exists an open file already referring to path. + * EACCES: path is a char device (nul:, com1:, etc.) + * + * Side effects: + * The file is deleted, even if it is read-only. + * + *--------------------------------------------------------------------------- + */ + +int +TclpObjDeleteFile( + Tcl_Obj *pathPtr) +{ + return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr)); +} + +int +TclpDeleteFile( + const void *nativePath) /* Pathname of file to be removed (native). */ +{ + DWORD attr; + const WCHAR *path = (const WCHAR *)nativePath; + + /* + * The DeleteFile API acts differently under Win95/98 and NT WRT NULL and + * "". Avoid passing these values. + */ + + if (path == NULL || path[0] == '\0') { + Tcl_SetErrno(ENOENT); + return TCL_ERROR; + } + + if (DeleteFileW(path) != FALSE) { + return TCL_OK; + } + Tcl_WinConvertError(GetLastError()); + + if (Tcl_GetErrno() == EACCES) { + attr = GetFileAttributesW(path); + if (attr != 0xFFFFFFFF) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { + /* + * It is a symbolic link - remove it. + */ + if (TclWinSymLinkDelete(path, 0) == 0) { + return TCL_OK; + } + } + + /* + * If we fall through here, it is a directory. + * + * Windows NT reports removing a directory as EACCES instead + * of EISDIR. + */ + + Tcl_SetErrno(EISDIR); + } else if (attr & FILE_ATTRIBUTE_READONLY) { + int res = SetFileAttributesW(path, + attr & ~((DWORD) FILE_ATTRIBUTE_READONLY)); + + if ((res != 0) && (DeleteFileW(path) != FALSE)) { + return TCL_OK; + } + Tcl_WinConvertError(GetLastError()); + if (res != 0) { + SetFileAttributesW(path, attr); + } + } + } + } else if (Tcl_GetErrno() == ENOENT) { + attr = GetFileAttributesW(path); + if (attr != 0xFFFFFFFF) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Windows 95 reports removing a directory as ENOENT instead + * of EISDIR. + */ + + Tcl_SetErrno(EISDIR); + } + } + } else if (Tcl_GetErrno() == EINVAL) { + /* + * Windows NT reports removing a char device as EINVAL instead of + * EACCES. + */ + + Tcl_SetErrno(EACCES); + } + + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpObjCreateDirectory -- + * + * Creates the specified directory. All parent directories of the + * specified directory must already exist. The directory is automatically + * created with permissions so that user can access the new directory and + * create new files or subdirectories in it. + * + * Results: + * If the directory was successfully created, returns TCL_OK. Otherwise + * the return value is TCL_ERROR and errno is set to indicate the error. + * Some possible values for errno are: + * + * EACCES: a parent directory can't be read and/or written. + * EEXIST: path already exists. + * ENOENT: a parent directory doesn't exist. + * + * Side effects: + * A directory is created. + * + *--------------------------------------------------------------------------- + */ + +int +TclpObjCreateDirectory( + Tcl_Obj *pathPtr) +{ + return DoCreateDirectory((const WCHAR *)Tcl_FSGetNativePath(pathPtr)); +} + +static int +DoCreateDirectory( + const WCHAR *nativePath) /* Pathname of directory to create (native). */ +{ + if (CreateDirectoryW(nativePath, NULL) == 0) { + DWORD error = GetLastError(); + + Tcl_WinConvertError(error); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpObjCopyDirectory -- + * + * Recursively copies a directory. The target directory dst must not + * already exist. Note that this function does not merge two directory + * hierarchies, even if the target directory is an empty directory. + * + * Results: + * If the directory was successfully copied, returns TCL_OK. Otherwise + * the return value is TCL_ERROR, errno is set to indicate the error, and + * the pathname of the file that caused the error is stored in errorPtr. + * See TclpCreateDirectory and TclpCopyFile for a description of possible + * values for errno. + * + * Side effects: + * An exact copy of the directory hierarchy src will be created with the + * name dst. If an error occurs, the error will be returned immediately, + * and remaining files will not be processed. + * + *--------------------------------------------------------------------------- + */ + +int +TclpObjCopyDirectory( + Tcl_Obj *srcPathPtr, + Tcl_Obj *destPathPtr, + Tcl_Obj **errorPtr) +{ + Tcl_DString ds; + Tcl_DString srcString, dstString; + Tcl_Obj *normSrcPtr, *normDestPtr; + int ret; + + normSrcPtr = Tcl_FSGetNormalizedPath(NULL,srcPathPtr); + if (normSrcPtr == NULL) { + return TCL_ERROR; + } + if (normSrcPtr != srcPathPtr) { Tcl_IncrRefCount(normSrcPtr); } + normDestPtr = Tcl_FSGetNormalizedPath(NULL,destPathPtr); + if (normDestPtr == NULL) { + if (normSrcPtr != srcPathPtr) { Tcl_DecrRefCount(normSrcPtr); } + return TCL_ERROR; + } + if (normDestPtr != destPathPtr) { Tcl_IncrRefCount(normDestPtr); } + + Tcl_DStringInit(&srcString); + Tcl_DStringInit(&dstString); + Tcl_UtfToWCharDString(TclGetString(normSrcPtr), TCL_INDEX_NONE, &srcString); + Tcl_UtfToWCharDString(TclGetString(normDestPtr), TCL_INDEX_NONE, &dstString); + + ret = TraverseWinTree(TraversalCopy, &srcString, &dstString, &ds); + + Tcl_DStringFree(&srcString); + Tcl_DStringFree(&dstString); + + if (ret != TCL_OK) { + if (!strcmp(Tcl_DStringValue(&ds), TclGetString(normSrcPtr))) { + *errorPtr = srcPathPtr; + } else if (!strcmp(Tcl_DStringValue(&ds), TclGetString(normDestPtr))) { + *errorPtr = destPathPtr; + } else { + *errorPtr = Tcl_DStringToObj(&ds); + } + Tcl_DStringFree(&ds); + Tcl_IncrRefCount(*errorPtr); + } + + if (normSrcPtr != srcPathPtr) { Tcl_DecrRefCount(normSrcPtr); } + if (normDestPtr != destPathPtr) { Tcl_DecrRefCount(normDestPtr); } + return ret; +} + +/* + *---------------------------------------------------------------------- + * + * TclpObjRemoveDirectory, DoRemoveDirectory -- + * + * Removes directory (and its contents, if the recursive flag is set). + * + * Results: + * If the directory was successfully removed, returns TCL_OK. Otherwise + * the return value is TCL_ERROR, errno is set to indicate the error, and + * the pathname of the file that caused the error is stored in errorPtr. + * Some possible values for errno are: + * + * EACCES: path directory can't be read and/or written. + * EEXIST: path is a non-empty directory. + * EINVAL: path is root directory or current directory. + * ENOENT: path doesn't exist or is "". + * ENOTDIR: path is not a directory. + * + * EACCES: path is a char device (nul:, com1:, etc.) (95) + * EINVAL: path is a char device (nul:, com1:, etc.) (NT) + * + * Side effects: + * Directory removed. If an error occurs, the error will be returned + * immediately, and remaining files will not be deleted. + * + *---------------------------------------------------------------------- + */ + +int +TclpObjRemoveDirectory( + Tcl_Obj *pathPtr, + int recursive, + Tcl_Obj **errorPtr) +{ + Tcl_DString ds; + Tcl_Obj *normPtr = NULL; + int ret; + + if (recursive) { + /* + * In the recursive case, the string rep is used to construct a + * Tcl_DString which may be used extensively, so we can't optimize + * this case easily. + */ + + Tcl_DString native; + normPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); + if (normPtr == NULL) { + return TCL_ERROR; + } + if (normPtr != pathPtr) { Tcl_IncrRefCount(normPtr); } + Tcl_DStringInit(&native); + Tcl_UtfToWCharDString(TclGetString(normPtr), TCL_INDEX_NONE, &native); + ret = DoRemoveDirectory(&native, recursive, &ds); + Tcl_DStringFree(&native); + } else { + ret = DoRemoveJustDirectory((const WCHAR *)Tcl_FSGetNativePath(pathPtr), 0, &ds); + } + + if (ret != TCL_OK) { + if (Tcl_DStringLength(&ds) > 0) { + if (normPtr != NULL && + !strcmp(Tcl_DStringValue(&ds), TclGetString(normPtr))) { + *errorPtr = pathPtr; + } else { + *errorPtr = Tcl_DStringToObj(&ds); + } + Tcl_IncrRefCount(*errorPtr); + } + Tcl_DStringFree(&ds); + } + if (normPtr && normPtr != pathPtr) { Tcl_DecrRefCount(normPtr); } + + return ret; +} + +static int +DoRemoveJustDirectory( + const WCHAR *nativePath, /* Pathname of directory to be removed + * (native). */ + int ignoreError, /* If non-zero, don't initialize the errorPtr + * under some circumstances on return. */ + Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free DString + * filled with UTF-8 name of file causing + * error. */ +{ + DWORD attr; + + /* + * The RemoveDirectory API acts differently under Win95/98 and NT WRT NULL + * and "". Avoid passing these values. + */ + + if (nativePath == NULL || nativePath[0] == '\0') { + Tcl_SetErrno(ENOENT); + Tcl_DStringInit(errorPtr); + return TCL_ERROR; + } + + attr = GetFileAttributesW(nativePath); + + if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { + /* + * It is a symbolic link - remove it. + */ + if (TclWinSymLinkDelete(nativePath, 0) == 0) { + return TCL_OK; + } + } else { + /* + * Ordinary directory. + */ + + if (RemoveDirectoryW(nativePath) != FALSE) { + return TCL_OK; + } + } + + Tcl_WinConvertError(GetLastError()); + + if (Tcl_GetErrno() == EACCES) { + attr = GetFileAttributesW(nativePath); + if (attr != 0xFFFFFFFF) { + if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + /* + * Windows 95 reports calling RemoveDirectory on a file as an + * EACCES, not an ENOTDIR. + */ + + Tcl_SetErrno(ENOTDIR); + goto end; + } + + if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { + /* + * It is a symbolic link - remove it. + */ + + if (TclWinSymLinkDelete(nativePath, 1) != 0) { + goto end; + } + } + + if (attr & FILE_ATTRIBUTE_READONLY) { + attr &= ~FILE_ATTRIBUTE_READONLY; + if (SetFileAttributesW(nativePath, attr) == FALSE) { + goto end; + } + if (RemoveDirectoryW(nativePath) != FALSE) { + return TCL_OK; + } + Tcl_WinConvertError(GetLastError()); + SetFileAttributesW(nativePath, + attr | FILE_ATTRIBUTE_READONLY); + } + } + } + + if (Tcl_GetErrno() == ENOTEMPTY) { + /* + * The caller depends on EEXIST to signify that the directory is not + * empty, not ENOTEMPTY. + */ + + Tcl_SetErrno(EEXIST); + } + + if ((ignoreError != 0) && (Tcl_GetErrno() == EEXIST)) { + /* + * If we're being recursive, this error may actually be ok, so we + * don't want to initialise the errorPtr yet. + */ + return TCL_ERROR; + } + + end: + if (errorPtr != NULL) { + char *p; + + Tcl_DStringInit(errorPtr); + p = Tcl_WCharToUtfDString(nativePath, TCL_INDEX_NONE, errorPtr); + for (; *p; ++p) { + if (*p == '\\') { + *p = '/'; + } + } + } + return TCL_ERROR; +} + +static int +DoRemoveDirectory( + Tcl_DString *pathPtr, /* Pathname of directory to be removed + * (native). */ + int recursive, /* If non-zero, removes directories that are + * nonempty. Otherwise, will only remove empty + * directories. */ + Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free DString + * filled with UTF-8 name of file causing + * error. */ +{ + int res = DoRemoveJustDirectory((const WCHAR *)Tcl_DStringValue(pathPtr), recursive, + errorPtr); + + if ((res == TCL_ERROR) && (recursive != 0) && (Tcl_GetErrno() == EEXIST)) { + /* + * The directory is nonempty, but the recursive flag has been + * specified, so we recursively remove all the files in the directory. + */ + + return TraverseWinTree(TraversalDelete, pathPtr, NULL, errorPtr); + } else { + return res; + } +} + +/* + *--------------------------------------------------------------------------- + * + * TraverseWinTree -- + * + * Traverse directory tree specified by sourcePtr, calling the function + * traverseProc for each file and directory encountered. If destPtr is + * non-null, each of name in the sourcePtr directory is appended to the + * directory specified by destPtr and passed as the second argument to + * traverseProc(). + * + * Results: + * Standard Tcl result. + * + * Side effects: + * None caused by TraverseWinTree, however the user specified + * traverseProc() may change state. If an error occurs, the error will be + * returned immediately, and remaining files will not be processed. + * + *--------------------------------------------------------------------------- + */ + +static int +TraverseWinTree( + TraversalProc *traverseProc,/* Function to call for every file and + * directory in source hierarchy. */ + Tcl_DString *sourcePtr, /* Pathname of source directory to be + * traversed (native). */ + Tcl_DString *targetPtr, /* Pathname of directory to traverse in + * parallel with source directory (native), + * may be NULL. */ + Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free DString + * filled with UTF-8 name of file causing + * error. */ +{ + DWORD sourceAttr; + WCHAR *nativeSource, *nativeTarget, *nativeErrfile; + int result, found; + Tcl_Size sourceLen, oldSourceLen, oldTargetLen, targetLen = 0; + HANDLE handle; + WIN32_FIND_DATAW data; + + nativeErrfile = NULL; + result = TCL_OK; + oldTargetLen = 0; + + nativeSource = (WCHAR *) Tcl_DStringValue(sourcePtr); + nativeTarget = (WCHAR *) + (targetPtr == NULL ? NULL : Tcl_DStringValue(targetPtr)); + + oldSourceLen = Tcl_DStringLength(sourcePtr); + sourceAttr = GetFileAttributesW(nativeSource); + if (sourceAttr == 0xFFFFFFFF) { + nativeErrfile = nativeSource; + goto end; + } + + if (sourceAttr & FILE_ATTRIBUTE_REPARSE_POINT) { + /* + * Process the symbolic link + */ + + return traverseProc(nativeSource, nativeTarget, DOTREE_LINK, + errorPtr); + } + + if ((sourceAttr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + /* + * Process the regular file + */ + + return traverseProc(nativeSource, nativeTarget, DOTREE_F, errorPtr); + } + + Tcl_DStringAppend(sourcePtr, (char *) L"\\*.*", 4 * sizeof(WCHAR) + 1); + Tcl_DStringSetLength(sourcePtr, Tcl_DStringLength(sourcePtr) - 1); + + nativeSource = (WCHAR *) Tcl_DStringValue(sourcePtr); + handle = FindFirstFileW(nativeSource, &data); + if (handle == INVALID_HANDLE_VALUE) { + /* + * Can't read directory. + */ + + Tcl_WinConvertError(GetLastError()); + nativeErrfile = nativeSource; + goto end; + } + + Tcl_DStringSetLength(sourcePtr, oldSourceLen + 1); + Tcl_DStringSetLength(sourcePtr, oldSourceLen); + result = traverseProc(nativeSource, nativeTarget, DOTREE_PRED, + errorPtr); + if (result != TCL_OK) { + FindClose(handle); + return result; + } + + sourceLen = oldSourceLen + sizeof(WCHAR); + Tcl_DStringAppend(sourcePtr, (char *) L"\\", sizeof(WCHAR) + 1); + Tcl_DStringSetLength(sourcePtr, sourceLen); + if (targetPtr != NULL) { + oldTargetLen = Tcl_DStringLength(targetPtr); + + targetLen = oldTargetLen; + targetLen += sizeof(WCHAR); + Tcl_DStringAppend(targetPtr, (char *) L"\\", sizeof(WCHAR) + 1); + Tcl_DStringSetLength(targetPtr, targetLen); + } + + found = 1; + for (; found; found = FindNextFileW(handle, &data)) { + WCHAR *nativeName; + size_t len; + + WCHAR *wp = data.cFileName; + if (*wp == '.') { + wp++; + if (*wp == '.') { + wp++; + } + if (*wp == '\0') { + continue; + } + } + nativeName = (WCHAR *) data.cFileName; + len = wcslen(data.cFileName) * sizeof(WCHAR); + + /* + * Append name after slash, and recurse on the file. + */ + + Tcl_DStringAppend(sourcePtr, (char *) nativeName, len + 1); + Tcl_DStringSetLength(sourcePtr, Tcl_DStringLength(sourcePtr) - 1); + if (targetPtr != NULL) { + Tcl_DStringAppend(targetPtr, (char *) nativeName, len + 1); + Tcl_DStringSetLength(targetPtr, Tcl_DStringLength(targetPtr) - 1); + } + result = TraverseWinTree(traverseProc, sourcePtr, targetPtr, + errorPtr); + if (result != TCL_OK) { + break; + } + + /* + * Remove name after slash. + */ + + Tcl_DStringSetLength(sourcePtr, sourceLen); + if (targetPtr != NULL) { + Tcl_DStringSetLength(targetPtr, targetLen); + } + } + FindClose(handle); + + /* + * Strip off the trailing slash we added. + */ + + Tcl_DStringSetLength(sourcePtr, oldSourceLen + 1); + Tcl_DStringSetLength(sourcePtr, oldSourceLen); + if (targetPtr != NULL) { + Tcl_DStringSetLength(targetPtr, oldTargetLen + 1); + Tcl_DStringSetLength(targetPtr, oldTargetLen); + } + if (result == TCL_OK) { + /* + * Call traverseProc() on a directory after visiting all the + * files in that directory. + */ + + result = traverseProc((const WCHAR *)Tcl_DStringValue(sourcePtr), + (const WCHAR *)(targetPtr == NULL ? NULL : Tcl_DStringValue(targetPtr)), + DOTREE_POSTD, errorPtr); + } + + end: + if (nativeErrfile != NULL) { + Tcl_WinConvertError(GetLastError()); + if (errorPtr != NULL) { + Tcl_DStringInit(errorPtr); + Tcl_WCharToUtfDString(nativeErrfile, TCL_INDEX_NONE, errorPtr); + } + result = TCL_ERROR; + } + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TraversalCopy + * + * Called from TraverseUnixTree in order to execute a recursive copy of a + * directory. + * + * Results: + * Standard Tcl result. + * + * Side effects: + * Depending on the value of type, src may be copied to dst. + * + *---------------------------------------------------------------------- + */ + +static int +TraversalCopy( + const WCHAR *nativeSrc, /* Source pathname to copy. */ + const WCHAR *nativeDst, /* Destination pathname of copy. */ + int type, /* Reason for call - see TraverseWinTree() */ + Tcl_DString *errorPtr) /* If non-NULL, initialized DString filled + * with UTF-8 name of file causing error. */ +{ + switch (type) { + case DOTREE_F: + if (DoCopyFile(nativeSrc, nativeDst) == TCL_OK) { + return TCL_OK; + } + break; + case DOTREE_LINK: + if (TclWinSymLinkCopyDirectory(nativeSrc, nativeDst) == TCL_OK) { + return TCL_OK; + } + break; + case DOTREE_PRED: + if (DoCreateDirectory(nativeDst) == TCL_OK) { + DWORD attr = GetFileAttributesW(nativeSrc); + + if (SetFileAttributesW(nativeDst, attr) != FALSE) { + return TCL_OK; + } + Tcl_WinConvertError(GetLastError()); + } + break; + case DOTREE_POSTD: + return TCL_OK; + } + + /* + * There shouldn't be a problem with src, because we already checked it to + * get here. + */ + + if (errorPtr != NULL) { + Tcl_DStringInit(errorPtr); + Tcl_WCharToUtfDString(nativeDst, TCL_INDEX_NONE, errorPtr); + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * TraversalDelete -- + * + * Called by function TraverseWinTree for every file and directory that + * it encounters in a directory hierarchy. This function unlinks files, + * and removes directories after all the containing files have been + * processed. + * + * Results: + * Standard Tcl result. + * + * Side effects: + * Files or directory specified by src will be deleted. If an error + * occurs, the windows error is converted to a Posix error and errno is + * set accordingly. + * + *---------------------------------------------------------------------- + */ + +static int +TraversalDelete( + const WCHAR *nativeSrc, /* Source pathname to delete. */ + TCL_UNUSED(const WCHAR *) /*dstPtr*/, + int type, /* Reason for call - see TraverseWinTree() */ + Tcl_DString *errorPtr) /* If non-NULL, initialized DString filled + * with UTF-8 name of file causing error. */ +{ + switch (type) { + case DOTREE_F: + if (TclpDeleteFile(nativeSrc) == TCL_OK) { + return TCL_OK; + } + break; + case DOTREE_LINK: + if (DoRemoveJustDirectory(nativeSrc, 0, NULL) == TCL_OK) { + return TCL_OK; + } + break; + case DOTREE_PRED: + return TCL_OK; + case DOTREE_POSTD: + if (DoRemoveJustDirectory(nativeSrc, 0, NULL) == TCL_OK) { + return TCL_OK; + } + break; + } + + if (errorPtr != NULL) { + Tcl_DStringInit(errorPtr); + Tcl_WCharToUtfDString(nativeSrc, TCL_INDEX_NONE, errorPtr); + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * StatError -- + * + * Sets the object result with the appropriate error. + * + * Results: + * None. + * + * Side effects: + * The interp's object result is set with an error message based on the + * objIndex, fileName and errno. + * + *---------------------------------------------------------------------- + */ + +static void +StatError( + Tcl_Interp *interp, /* The interp that has the error */ + Tcl_Obj *fileName) /* The name of the file which caused the + * error. */ +{ + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("could not read \"%s\": %s", + TclGetString(fileName), Tcl_PosixError(interp))); +} + +/* + *---------------------------------------------------------------------- + * + * GetWinFileAttributes -- + * + * Returns a Tcl_Obj containing the value of a file attribute. This + * routine gets the -hidden, -readonly or -system attribute. + * + * Results: + * Standard Tcl result and a Tcl_Obj in attributePtrPtr. The object will + * have ref count 0. If the return value is not TCL_OK, attributePtrPtr + * is not touched. + * + * Side effects: + * A new object is allocated if the file is valid. + * + *---------------------------------------------------------------------- + */ + +static int +GetWinFileAttributes( + 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. */ + Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ +{ + DWORD result; + const WCHAR *nativeName; + int attr; + + nativeName = (const WCHAR *)Tcl_FSGetNativePath(fileName); + result = GetFileAttributesW(nativeName); + + if (result == 0xFFFFFFFF) { + StatError(interp, fileName); + return TCL_ERROR; + } + + attr = (int)(result & attributeArray[objIndex]); + if ((objIndex == WIN_HIDDEN_ATTRIBUTE) && (attr != 0)) { + /* + * It is hidden. However there is a bug on some Windows OSes in which + * root volumes (drives) formatted as NTFS are declared hidden when + * they are not (and cannot be). + * + * We test for, and fix that case, here. + */ + + Tcl_Size len; + const char *str = TclGetStringFromObj(fileName, &len); + + if (len < 4) { + if (len == 0) { + /* + * Not sure if this is possible, but we pass it on anyway. + */ + } else if (len == 1 && (str[0] == '/' || str[0] == '\\')) { + /* + * Path is pointing to the root volume. + */ + + attr = 0; + } else if ((str[1] == ':') + && (len == 2 || (str[2] == '/' || str[2] == '\\'))) { + /* + * Path is of the form 'x:' or 'x:/' or 'x:\' + */ + + attr = 0; + } + } + } + + TclNewIntObj(*attributePtrPtr, attr != 0); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ConvertFileNameFormat -- + * + * Returns a Tcl_Obj containing either the long or short version of the + * file name. + * + * Results: + * Standard Tcl result and a Tcl_Obj in attributePtrPtr. The object will + * have ref count 0. If the return value is not TCL_OK, attributePtrPtr + * is not touched. + * + * Warning: if you pass this function a drive name like 'c:' it will + * actually return the current working directory on that drive. To avoid + * this, make sure the drive name ends in a slash, like this 'c:/'. + * + * Side effects: + * A new object is allocated if the file is valid. + * + *---------------------------------------------------------------------- + */ + +static int +ConvertFileNameFormat( + Tcl_Interp *interp, /* The interp we are using for errors. */ + TCL_UNUSED(int) /*objIndex*/, + Tcl_Obj *fileName, /* The name of the file. */ + int longShort, /* 0 to short name, 1 to long name. */ + Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ +{ + Tcl_Size pathc, i, length; + Tcl_Obj *splitPath; + + splitPath = Tcl_FSSplitPath(fileName, &pathc); + + if (splitPath == NULL || pathc == 0) { + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "could not read \"%s\": no such file or directory", + TclGetString(fileName))); + errno = ENOENT; + Tcl_PosixError(interp); + } + goto cleanup; + } + + /* + * We will decrement this again at the end. It is safer to do this in + * case any of the calls below retain a reference to splitPath. + */ + + Tcl_IncrRefCount(splitPath); + + for (i = 0; i < pathc; i++) { + Tcl_Obj *elt; + char *pathv; + + Tcl_ListObjIndex(NULL, splitPath, i, &elt); + + pathv = TclGetStringFromObj(elt, &length); + if ((pathv[0] == '/') || ((length == 3) && (pathv[1] == ':')) + || (strcmp(pathv, ".") == 0) || (strcmp(pathv, "..") == 0)) { + /* + * Handle "/", "//machine/export", "c:/", "." or ".." by just + * copying the string literally. Uppercase the drive letter, just + * because it looks better under Windows to do so. + */ + + simple: + /* + * Here we are modifying the string representation in place. + * + * I believe this is legal, since this won't affect any file + * representation this thing may have. + */ + + pathv[0] = (char) Tcl_UniCharToUpper(UCHAR(pathv[0])); + } else { + Tcl_Obj *tempPath; + Tcl_DString ds; + Tcl_DString dsTemp; + const WCHAR *nativeName; + const char *tempString; + WIN32_FIND_DATAW data; + HANDLE handle; + DWORD attr; + + tempPath = Tcl_FSJoinPath(splitPath, i+1); + Tcl_IncrRefCount(tempPath); + + /* + * We'd like to call Tcl_FSGetNativePath(tempPath) but that is + * likely to lead to infinite loops. + */ + + tempString = TclGetStringFromObj(tempPath, &length); + Tcl_DStringInit(&ds); + nativeName = Tcl_UtfToWCharDString(tempString, length, &ds); + Tcl_DecrRefCount(tempPath); + handle = FindFirstFileW(nativeName, &data); + if (handle == INVALID_HANDLE_VALUE) { + /* + * FindFirstFileW() doesn't like root directories. We would + * only get a root directory here if the caller specified "c:" + * or "c:." and the current directory on the drive was the + * root directory + */ + + attr = GetFileAttributesW(nativeName); + if ((attr!=0xFFFFFFFF) && (attr & FILE_ATTRIBUTE_DIRECTORY)) { + Tcl_DStringFree(&ds); + goto simple; + } + } + + if (handle == INVALID_HANDLE_VALUE) { + Tcl_DStringFree(&ds); + if (interp != NULL) { + StatError(interp, fileName); + } + goto cleanup; + } + nativeName = data.cAlternateFileName; + if (longShort) { + if (data.cFileName[0] != '\0') { + nativeName = data.cFileName; + } + } else { + if (data.cAlternateFileName[0] == '\0') { + nativeName = (WCHAR *) data.cFileName; + } + } + + /* + * Purify reports a extraneous UMR in Tcl_WCharToUtfDString() trying + * to dereference nativeName as a Unicode string. I have proven to + * myself that purify is wrong by running the following example + * when nativeName == data.w.cAlternateFileName and noting that + * purify doesn't complain about the first line, but does complain + * about the second. + * + * fprintf(stderr, "%d\n", data.w.cAlternateFileName[0]); + * fprintf(stderr, "%d\n", ((WCHAR *) nativeName)[0]); + */ + + Tcl_DStringInit(&dsTemp); + Tcl_WCharToUtfDString(nativeName, TCL_INDEX_NONE, &dsTemp); + Tcl_DStringFree(&ds); + + tempPath = Tcl_DStringToObj(&dsTemp); + Tcl_ListObjReplace(NULL, splitPath, i, 1, 1, &tempPath); + FindClose(handle); + } + } + + *attributePtrPtr = Tcl_FSJoinPath(splitPath, TCL_INDEX_NONE); + + if (splitPath != NULL) { + /* + * Unfortunately, the object we will return may have its only refCount + * as part of the list splitPath. This means if we free splitPath, the + * object will disappear. So, we have to be very careful here. + * Unfortunately this means we must manipulate the object's refCount + * directly. + */ + + Tcl_IncrRefCount(*attributePtrPtr); + Tcl_DecrRefCount(splitPath); + --(*attributePtrPtr)->refCount; + } + return TCL_OK; + + cleanup: + if (splitPath != NULL) { + Tcl_DecrRefCount(splitPath); + } + + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * GetWinFileLongName -- + * + * Returns a Tcl_Obj containing the long version of the file name. + * + * Results: + * Standard Tcl result and a Tcl_Obj in attributePtrPtr. The object will + * have ref count 0. If the return value is not TCL_OK, attributePtrPtr + * is not touched. + * + * Side effects: + * A new object is allocated if the file is valid. + * + *---------------------------------------------------------------------- + */ + +static int +GetWinFileLongName( + 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. */ + Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ +{ + return ConvertFileNameFormat(interp, objIndex, fileName, 1, + attributePtrPtr); +} + +/* + *---------------------------------------------------------------------- + * + * GetWinFileShortName -- + * + * Returns a Tcl_Obj containing the short version of the file name. + * + * Results: + * Standard Tcl result and a Tcl_Obj in attributePtrPtr. The object will + * have ref count 0. If the return value is not TCL_OK, attributePtrPtr + * is not touched. + * + * Side effects: + * A new object is allocated if the file is valid. + * + *---------------------------------------------------------------------- + */ + +static int +GetWinFileShortName( + 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. */ + Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ +{ + return ConvertFileNameFormat(interp, objIndex, fileName, 0, + attributePtrPtr); +} + +/* + *---------------------------------------------------------------------- + * + * SetWinFileAttributes -- + * + * Set the file attributes to the value given by attributePtr. This + * routine sets the -hidden, -readonly, or -system attributes. + * + * Results: + * Standard TCL error. + * + * Side effects: + * The file's attribute is set. + * + *---------------------------------------------------------------------- + */ + +static int +SetWinFileAttributes( + 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. */ + Tcl_Obj *attributePtr) /* The new value of the attribute. */ +{ + DWORD fileAttributes, old; + int yesNo, result; + const WCHAR *nativeName; + + nativeName = (const WCHAR *)Tcl_FSGetNativePath(fileName); + fileAttributes = old = GetFileAttributesW(nativeName); + + if (fileAttributes == 0xFFFFFFFF) { + StatError(interp, fileName); + return TCL_ERROR; + } + + result = Tcl_GetBooleanFromObj(interp, attributePtr, &yesNo); + if (result != TCL_OK) { + return result; + } + + if (yesNo) { + fileAttributes |= (attributeArray[objIndex]); + } else { + fileAttributes &= ~(attributeArray[objIndex]); + } + + if ((fileAttributes != old) + && !SetFileAttributesW(nativeName, fileAttributes)) { + StatError(interp, fileName); + return TCL_ERROR; + } + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * SetWinFileLongName -- + * + * The attribute in question is a readonly attribute and cannot be set. + * + * Results: + * TCL_ERROR + * + * Side effects: + * The object result is set to a pertinent error message. + * + *---------------------------------------------------------------------- + */ + +static int +CannotSetAttribute( + 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. */ + TCL_UNUSED(Tcl_Obj *) /*attributePtr*/) +{ + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "cannot set attribute \"%s\" for file \"%s\": attribute is readonly", + tclpFileAttrStrings[objIndex], TclGetString(fileName))); + errno = EINVAL; + Tcl_PosixError(interp); + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpObjListVolumes -- + * + * Lists the currently mounted volumes + * + * Results: + * The list of volumes. + * + * Side effects: + * None + * + *--------------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpObjListVolumes(void) +{ + Tcl_Obj *resultPtr, *elemPtr; + char buf[40 * 4]; /* There couldn't be more than 30 drives??? */ + int i; + char *p; + + TclNewObj(resultPtr); + + /* + * On Win32s: + * GetLogicalDriveStrings() isn't implemented. + * GetLogicalDrives() returns incorrect information. + */ + + if (GetLogicalDriveStringsA(sizeof(buf), buf) == 0) { + /* + * GetVolumeInformationW() will detects all drives, but causes + * chattering on empty floppy drives. We only do this if + * GetLogicalDriveStrings() didn't work. It has also been reported + * that on some laptops it takes a while for GetVolumeInformationW() to + * return when pinging an empty floppy drive, another reason to try to + * avoid calling it. + */ + + buf[1] = ':'; + buf[2] = '/'; + buf[3] = '\0'; + + for (i = 0; i < 26; i++) { + buf[0] = (char) ('a' + i); + if (GetVolumeInformationA(buf, NULL, 0, NULL, NULL, NULL, NULL, 0) + || (GetLastError() == ERROR_NOT_READY)) { + elemPtr = Tcl_NewStringObj(buf, TCL_INDEX_NONE); + Tcl_ListObjAppendElement(NULL, resultPtr, elemPtr); + } + } + } else { + for (p = buf; *p != '\0'; p += 4) { + p[2] = '/'; + elemPtr = Tcl_NewStringObj(p, TCL_INDEX_NONE); + Tcl_ListObjAppendElement(NULL, resultPtr, elemPtr); + } + } + + Tcl_IncrRefCount(resultPtr); + return resultPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreateTemporaryDirectory -- + * + * Creates a temporary directory, possibly based on the supplied bits and + * pieces of template supplied in the arguments. + * + * Results: + * An object (refcount 0) containing the name of the newly-created + * directory, or NULL on failure. + * + * Side effects: + * Accesses the native filesystem. Makes a directory. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpCreateTemporaryDirectory( + Tcl_Obj *dirObj, + Tcl_Obj *basenameObj) +{ + Tcl_DString base, name; /* Contains WCHARs */ + Tcl_Size baseLen; + DWORD error; + WCHAR tempBuf[MAX_PATH + 1]; + DWORD len = GetTempPathW(MAX_PATH, tempBuf); + + /* + * Build the path in writable memory from the user-supplied pieces and + * some defaults. First, the parent temporary directory. + */ + + if (dirObj) { + TclGetString(dirObj); + if (dirObj->length < 1) { + goto useSystemTemp; + } + Tcl_DStringInit(&base); + Tcl_UtfToWCharDString(TclGetString(dirObj), TCL_INDEX_NONE, &base); + if (dirObj->bytes[dirObj->length - 1] != '\\') { + Tcl_UtfToWCharDString("\\", TCL_INDEX_NONE, &base); + } + } else { + useSystemTemp: + Tcl_DStringInit(&base); + Tcl_DStringAppend(&base, (char *) tempBuf, len * sizeof(WCHAR)); + } + + /* + * Next, the base of the directory name. + */ + +#define DEFAULT_TEMP_DIR_PREFIX "tcl" +#define SUFFIX_LENGTH 8 + + if (basenameObj) { + Tcl_UtfToWCharDString(TclGetString(basenameObj), TCL_INDEX_NONE, &base); + } else { + Tcl_UtfToWCharDString(DEFAULT_TEMP_DIR_PREFIX, TCL_INDEX_NONE, &base); + } + Tcl_UtfToWCharDString("_", TCL_INDEX_NONE, &base); + + /* + * Now we keep on trying random suffixes until we get one that works + * (i.e., that doesn't trigger the ERROR_ALREADY_EXISTS error). Note that + * SUFFIX_LENGTH is longer than on Unix because we expect to be not on a + * case-sensitive filesystem. + */ + + baseLen = Tcl_DStringLength(&base); + do { + char tempbuf[SUFFIX_LENGTH + 1]; + int i; + static const char randChars[] = + "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"; + static const int numRandChars = sizeof(randChars) - 1; + + /* + * Put a random suffix on the end. + */ + + error = ERROR_SUCCESS; + tempbuf[SUFFIX_LENGTH] = '\0'; + for (i = 0 ; i < SUFFIX_LENGTH; i++) { + tempbuf[i] = randChars[(int) (rand() % numRandChars)]; + } + Tcl_DStringSetLength(&base, baseLen); + Tcl_UtfToWCharDString(tempbuf, TCL_INDEX_NONE, &base); + } while (!CreateDirectoryW((LPCWSTR) Tcl_DStringValue(&base), NULL) + && (error = GetLastError()) == ERROR_ALREADY_EXISTS); + + /* + * Check for other errors. The big ones are ERROR_PATH_NOT_FOUND and + * ERROR_ACCESS_DENIED. + */ + + if (error != ERROR_SUCCESS) { + Tcl_WinConvertError(error); + Tcl_DStringFree(&base); + return NULL; + } + + /* + * We actually made the directory, so we're done! Report what we made back + * as a (clean) Tcl_Obj. + */ + + Tcl_DStringInit(&name); + Tcl_WCharToUtfDString((LPCWSTR) Tcl_DStringValue(&base), TCL_INDEX_NONE, &name); + Tcl_DStringFree(&base); + return Tcl_DStringToObj(&name); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclWinFile.c b/vendor/tcl/win/tclWinFile.c new file mode 100644 index 00000000..8927e958 --- /dev/null +++ b/vendor/tcl/win/tclWinFile.c @@ -0,0 +1,3367 @@ +/* + * tclWinFile.c -- + * + * This file contains temporary wrappers around UNIX file handling + * functions. These wrappers map the UNIX functions to Win32 HANDLE-style + * files, which can be manipulated through the Win32 console redirection + * interfaces. + * + * Copyright © 1995-1998 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" +#include "tclFileSystem.h" +#include +#include +#include /* For TclpGetUserHome(). */ +#include /* For TclpGetUserHome(). */ +#include /* For GetNamedSecurityInfo */ +#if defined (__clang__) && (__clang_major__ > 20) +#pragma clang diagnostic ignored "-Wc++-keyword" +#endif +#ifdef _MSC_VER +# pragma comment(lib, "userenv.lib") +#endif +/* + * The number of 100-ns intervals between the Windows system epoch (1601-01-01 + * on the proleptic Gregorian calendar) and the Posix epoch (1970-01-01). + */ + +#define POSIX_EPOCH_AS_FILETIME 116444736000000000LL + +/* + * Declarations for 'link' related information. This information should come + * with VC++ 6.0, but is not in some older SDKs. In any case it is not well + * documented. + */ + +#ifndef IO_REPARSE_TAG_RESERVED_ONE +# define IO_REPARSE_TAG_RESERVED_ONE 0x000000001 +#endif +#ifndef IO_REPARSE_TAG_RESERVED_RANGE +# define IO_REPARSE_TAG_RESERVED_RANGE 0x000000001 +#endif +#ifndef IO_REPARSE_TAG_VALID_VALUES +# define IO_REPARSE_TAG_VALID_VALUES 0x0E000FFFF +#endif +#ifndef IO_REPARSE_TAG_HSM +# define IO_REPARSE_TAG_HSM 0x0C0000004 +#endif +#ifndef IO_REPARSE_TAG_NSS +# define IO_REPARSE_TAG_NSS 0x080000005 +#endif +#ifndef IO_REPARSE_TAG_NSSRECOVER +# define IO_REPARSE_TAG_NSSRECOVER 0x080000006 +#endif +#ifndef IO_REPARSE_TAG_SIS +# define IO_REPARSE_TAG_SIS 0x080000007 +#endif +#ifndef IO_REPARSE_TAG_DFS +# define IO_REPARSE_TAG_DFS 0x080000008 +#endif + +#ifndef IO_REPARSE_TAG_RESERVED_ZERO +# define IO_REPARSE_TAG_RESERVED_ZERO 0x00000000 +#endif +#ifndef FILE_FLAG_OPEN_REPARSE_POINT +# define FILE_FLAG_OPEN_REPARSE_POINT 0x00200000 +#endif +#ifndef IO_REPARSE_TAG_MOUNT_POINT +# define IO_REPARSE_TAG_MOUNT_POINT 0xA0000003 +#endif +#ifndef IsReparseTagValid +# define IsReparseTagValid(x) \ + (!((x)&~IO_REPARSE_TAG_VALID_VALUES)&&((x)>IO_REPARSE_TAG_RESERVED_RANGE)) +#endif +#ifndef IO_REPARSE_TAG_SYMBOLIC_LINK +# define IO_REPARSE_TAG_SYMBOLIC_LINK IO_REPARSE_TAG_RESERVED_ZERO +#endif +#ifndef FILE_SPECIAL_ACCESS +# define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS) +#endif +#ifndef FSCTL_SET_REPARSE_POINT +# define FSCTL_SET_REPARSE_POINT \ + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) +# define FSCTL_GET_REPARSE_POINT \ + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) +# define FSCTL_DELETE_REPARSE_POINT \ + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) +#endif +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif + +/* + * Maximum reparse buffer info size. The max user defined reparse data is + * 16KB, plus there's a header. + */ + +#define MAX_REPARSE_SIZE 17000 + +/* + * Undocumented REPARSE_MOUNTPOINT_HEADER_SIZE structure definition. This is + * found in winnt.h. + * + * IMPORTANT: caution when using this structure, since the actual structures + * used will want to store a full path in the 'PathBuffer' field, but there + * isn't room (there's only a single WCHAR!). Therefore one must artificially + * create a larger space of memory and then cast it to this type. We use the + * 'DUMMY_REPARSE_BUFFER' struct just below to deal with this problem. + */ + +#define REPARSE_MOUNTPOINT_HEADER_SIZE 8 +#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE +typedef struct _REPARSE_DATA_BUFFER { + DWORD ReparseTag; + WORD ReparseDataLength; + WORD Reserved; + union { + struct { + WORD SubstituteNameOffset; + WORD SubstituteNameLength; + WORD PrintNameOffset; + WORD PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + WORD SubstituteNameOffset; + WORD SubstituteNameLength; + WORD PrintNameOffset; + WORD PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + BYTE DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER; +#endif + +typedef struct { + REPARSE_DATA_BUFFER dummy; + WCHAR dummyBuf[MAX_PATH * 3]; +} DUMMY_REPARSE_BUFFER; + +/* + * Other typedefs required by this code. + */ + +static __time64_t ToCTime(FILETIME fileTime); +static void FromCTime(__time64_t posixTime, FILETIME *fileTime); + +/* + * Declarations for local functions defined in this file: + */ + +static int NativeAccess(const WCHAR *path, int mode); +static int NativeDev(const WCHAR *path); +static int NativeStat(const WCHAR *path, Tcl_StatBuf *statPtr, + int checkLinks); +static unsigned short NativeStatMode(DWORD attr, int checkLinks, + int isExec); +static int NativeIsExec(const WCHAR *path); +static int NativeReadReparse(const WCHAR *LinkDirectory, + REPARSE_DATA_BUFFER *buffer, DWORD desiredAccess); +static int NativeWriteReparse(const WCHAR *LinkDirectory, + REPARSE_DATA_BUFFER *buffer); +static int NativeMatchType(int isDrive, DWORD attr, + const WCHAR *nativeName, Tcl_GlobTypeData *types); +static int WinIsDrive(const char *name, size_t nameLen); +static size_t WinIsReserved(const char *path); +static Tcl_Obj * WinReadLink(const WCHAR *LinkSource); +static Tcl_Obj * WinReadLinkDirectory(const WCHAR *LinkDirectory); +static int WinLink(const WCHAR *LinkSource, + const WCHAR *LinkTarget, int linkAction); +static int WinSymLinkDirectory(const WCHAR *LinkDirectory, + const WCHAR *LinkTarget); +MODULE_SCOPE void tclWinDebugPanic(const char *format, ...); + +/* + * Check if a Windows error code is one that might be returned for + * non-existent files + */ +static inline int +IsNoSuchFileError(DWORD winError) +{ + return (winError == ERROR_FILE_NOT_FOUND || + winError == ERROR_PATH_NOT_FOUND || + winError == ERROR_INVALID_NAME); +} + +/* + *-------------------------------------------------------------------- + * + * WinLink -- + * + * Make a link from source to target. + * + *-------------------------------------------------------------------- + */ + +static int +WinLink( + const WCHAR *linkSourcePath, + const WCHAR *linkTargetPath, + int linkAction) +{ + WCHAR tempFileName[MAX_PATH]; + WCHAR *tempFilePart; + DWORD attr; + + /* + * Get the full path referenced by the target. + */ + + if (!GetFullPathNameW(linkTargetPath, MAX_PATH, tempFileName, + &tempFilePart)) { + /* + * Invalid file. + */ + + Tcl_WinConvertError(GetLastError()); + return -1; + } + + /* + * Make sure source file doesn't exist. + */ + + attr = GetFileAttributesW(linkSourcePath); + if (attr != INVALID_FILE_ATTRIBUTES) { + Tcl_SetErrno(EEXIST); + return -1; + } + + /* + * Get the full path referenced by the source file/directory. + */ + + if (!GetFullPathNameW(linkSourcePath, MAX_PATH, tempFileName, + &tempFilePart)) { + /* + * Invalid file. + */ + + Tcl_WinConvertError(GetLastError()); + return -1; + } + + /* + * Check the target. + */ + + attr = GetFileAttributesW(linkTargetPath); + if (attr == INVALID_FILE_ATTRIBUTES) { + /* + * The target doesn't exist. + */ + + Tcl_WinConvertError(GetLastError()); + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + /* + * It is a file. + */ + + if (linkAction & TCL_CREATE_HARD_LINK) { + if (CreateHardLinkW(linkSourcePath, linkTargetPath, NULL)) { + /* + * Success! + */ + + return 0; + } + + Tcl_WinConvertError(GetLastError()); + } else if (linkAction & TCL_CREATE_SYMBOLIC_LINK) { + if (CreateSymbolicLinkW(linkSourcePath, linkTargetPath, + 0x2 /* SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE */)) { + /* + * Success! + */ + + return 0; + } else { + Tcl_WinConvertError(GetLastError()); + } + } else { + Tcl_SetErrno(ENODEV); + } + } else { + /* + * We've got a directory. Now check whether what we're trying to do is + * reasonable. + */ + + if (linkAction & TCL_CREATE_SYMBOLIC_LINK) { + return WinSymLinkDirectory(linkSourcePath, linkTargetPath); + + } else if (linkAction & TCL_CREATE_HARD_LINK) { + /* + * Can't hard link directories. + */ + + Tcl_SetErrno(EISDIR); + } else { + Tcl_SetErrno(ENODEV); + } + } + return -1; +} + +/* + *-------------------------------------------------------------------- + * + * WinReadLink -- + * + * What does 'LinkSource' point to? + * + *-------------------------------------------------------------------- + */ + +static Tcl_Obj * +WinReadLink( + const WCHAR *linkSourcePath) +{ + WCHAR tempFileName[MAX_PATH]; + WCHAR *tempFilePart; + DWORD attr; + + /* + * Get the full path referenced by the target. + */ + + if (!GetFullPathNameW(linkSourcePath, MAX_PATH, tempFileName, + &tempFilePart)) { + /* + * Invalid file. + */ + + Tcl_WinConvertError(GetLastError()); + return NULL; + } + + /* + * Make sure source file does exist. + */ + + attr = GetFileAttributesW(linkSourcePath); + if (attr == INVALID_FILE_ATTRIBUTES) { + /* + * The source doesn't exist. + */ + + Tcl_WinConvertError(GetLastError()); + return NULL; + + } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + /* + * It is a file - this is not yet supported. + */ + + Tcl_SetErrno(ENOTDIR); + return NULL; + } + + return WinReadLinkDirectory(linkSourcePath); +} + +/* + *-------------------------------------------------------------------- + * + * WinSymLinkDirectory -- + * + * This routine creates a NTFS junction, using the undocumented + * FSCTL_SET_REPARSE_POINT structure Win2K uses for mount points and + * junctions. + * + * Assumption that linkTargetPath is a valid, existing directory. + * + * Returns: + * Zero on success. + * + *-------------------------------------------------------------------- + */ + +static int +WinSymLinkDirectory( + const WCHAR *linkDirPath, + const WCHAR *linkTargetPath) +{ + DUMMY_REPARSE_BUFFER dummy; + REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy; + int len; + WCHAR nativeTarget[MAX_PATH]; + WCHAR *loop; + + /* + * Make the native target name. + */ + + memcpy(nativeTarget, L"\\??\\", 4 * sizeof(WCHAR)); + memcpy(nativeTarget + 4, linkTargetPath, + sizeof(WCHAR) * (1+wcslen((WCHAR *) linkTargetPath))); + len = wcslen(nativeTarget); + + /* + * We must have backslashes only. This is VERY IMPORTANT. If we have any + * forward slashes everything appears to work, but the resulting symlink + * is useless! + */ + + for (loop = nativeTarget; *loop != 0; loop++) { + if (*loop == '/') { + *loop = '\\'; + } + } + if ((nativeTarget[len-1] == '\\') && (nativeTarget[len-2] != ':')) { + nativeTarget[len-1] = 0; + } + + /* + * Build the reparse info. + */ + + memset(reparseBuffer, 0, sizeof(DUMMY_REPARSE_BUFFER)); + reparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + reparseBuffer->MountPointReparseBuffer.SubstituteNameLength = + wcslen(nativeTarget) * sizeof(WCHAR); + reparseBuffer->Reserved = 0; + reparseBuffer->MountPointReparseBuffer.PrintNameLength = 0; + reparseBuffer->MountPointReparseBuffer.PrintNameOffset = + reparseBuffer->MountPointReparseBuffer.SubstituteNameLength + + sizeof(WCHAR); + memcpy(reparseBuffer->MountPointReparseBuffer.PathBuffer, nativeTarget, + sizeof(WCHAR) + + reparseBuffer->MountPointReparseBuffer.SubstituteNameLength); + reparseBuffer->ReparseDataLength = + reparseBuffer->MountPointReparseBuffer.SubstituteNameLength+12; + + return NativeWriteReparse(linkDirPath, reparseBuffer); +} + +/* + *-------------------------------------------------------------------- + * + * TclWinSymLinkCopyDirectory -- + * + * Copy a Windows NTFS junction. This function assumes that LinkOriginal + * exists and is a valid junction point, and that LinkCopy does not + * exist. + * + * Returns: + * Zero on success. + * + *-------------------------------------------------------------------- + */ + +int +TclWinSymLinkCopyDirectory( + const WCHAR *linkOrigPath, /* Existing junction - reparse point */ + const WCHAR *linkCopyPath) /* Will become a duplicate junction */ +{ + DUMMY_REPARSE_BUFFER dummy; + REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy; + + if (NativeReadReparse(linkOrigPath, reparseBuffer, GENERIC_READ)) { + return -1; + } + return NativeWriteReparse(linkCopyPath, reparseBuffer); +} + +/* + *-------------------------------------------------------------------- + * + * TclWinSymLinkDelete -- + * + * Delete a Windows NTFS junction. Once the junction information is + * deleted, the filesystem object becomes an ordinary directory. Unless + * 'linkOnly' is given, that directory is also removed. + * + * Assumption that LinkOriginal is a valid, existing junction. + * + * Returns: + * Zero on success. + * + *-------------------------------------------------------------------- + */ + +int +TclWinSymLinkDelete( + const WCHAR *linkOrigPath, + int linkOnly) +{ + /* + * It is a symbolic link - remove it. + */ + + DUMMY_REPARSE_BUFFER dummy; + REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy; + HANDLE hFile; + DWORD returnedLength; + + memset(reparseBuffer, 0, sizeof(DUMMY_REPARSE_BUFFER)); + reparseBuffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + hFile = CreateFileW(linkOrigPath, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (hFile != INVALID_HANDLE_VALUE) { + if (!DeviceIoControl(hFile, FSCTL_DELETE_REPARSE_POINT, reparseBuffer, + REPARSE_MOUNTPOINT_HEADER_SIZE,NULL,0,&returnedLength,NULL)) { + /* + * Error setting junction. + */ + + Tcl_WinConvertError(GetLastError()); + CloseHandle(hFile); + } else { + CloseHandle(hFile); + if (!linkOnly) { + RemoveDirectoryW(linkOrigPath); + } + return 0; + } + } + return -1; +} + +/* + *-------------------------------------------------------------------- + * + * WinReadLinkDirectory -- + * + * This routine reads a NTFS junction, using the undocumented + * FSCTL_GET_REPARSE_POINT structure Win2K uses for mount points and + * junctions. + * + * Assumption that LinkDirectory is a valid, existing directory. + * + * Returns: + * A Tcl_Obj with refCount of 1 (i.e. owned by the caller), or NULL if + * anything went wrong. + * + * In the future we should enhance this to return a path object rather + * than a string. + * + *-------------------------------------------------------------------- + */ + +#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + +static Tcl_Obj * +WinReadLinkDirectory( + const WCHAR *linkDirPath) +{ + int attr, len, offset; + DUMMY_REPARSE_BUFFER dummy; + REPARSE_DATA_BUFFER *reparseBuffer = (REPARSE_DATA_BUFFER *) &dummy; + Tcl_Obj *retVal; + Tcl_DString ds; + const char *copy; + + attr = GetFileAttributesW(linkDirPath); + if (!(attr & FILE_ATTRIBUTE_REPARSE_POINT)) { + goto invalidError; + } + if (NativeReadReparse(linkDirPath, reparseBuffer, 0)) { + return NULL; + } + + switch (reparseBuffer->ReparseTag) { + case 0x80000000|IO_REPARSE_TAG_SYMBOLIC_LINK: + case IO_REPARSE_TAG_SYMBOLIC_LINK: + case IO_REPARSE_TAG_MOUNT_POINT: + /* + * Certain native path representations on Windows have a special + * prefix to indicate that they are to be treated specially. For + * example extremely long paths, or symlinks, or volumes mounted + * inside directories. + * + * There is an assumption in this code that 'wide' interfaces are + * being used (see tclWin32Dll.c), which is true for the only systems + * which support reparse tags at present. If that changes in the + * future, this code will have to be generalised. + */ + + offset = 0; + if (reparseBuffer->MountPointReparseBuffer.PathBuffer[0] == '\\') { + /* + * Check whether this is a mounted volume. + */ + + if (wcsncmp(reparseBuffer->MountPointReparseBuffer.PathBuffer, + L"\\??\\Volume{",11) == 0) { + char drive; + + /* + * There is some confusion between \??\ and \\?\ which we have + * to fix here. It doesn't seem very well documented. + */ + + reparseBuffer->MountPointReparseBuffer.PathBuffer[1] = '\\'; + + /* + * Check if a corresponding drive letter exists, and use that + * if it is found + */ + + drive = TclWinDriveLetterForVolMountPoint( + reparseBuffer->MountPointReparseBuffer.PathBuffer); + if (drive != -1) { + char driveSpec[3] = { + '\0', ':', '\0' + }; + + driveSpec[0] = drive; + retVal = Tcl_NewStringObj(driveSpec,2); + Tcl_IncrRefCount(retVal); + return retVal; + } + + /* + * This is actually a mounted drive, which doesn't exists as a + * DOS drive letter. This means the path isn't actually a + * link, although we partially treat it like one ('file type' + * will return 'link'), but then the link will actually just + * be treated like an ordinary directory. I don't believe any + * serious inconsistency will arise from this, but it is + * something to be aware of. + */ + + goto invalidError; + } else if (wcsncmp(reparseBuffer->MountPointReparseBuffer + .PathBuffer, L"\\\\?\\",4) == 0) { + /* + * Strip off the prefix. + */ + + offset = 4; + } else if (wcsncmp(reparseBuffer->MountPointReparseBuffer + .PathBuffer, L"\\??\\",4) == 0) { + /* + * Strip off the prefix. + */ + + offset = 4; + } + } + + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString( + reparseBuffer->MountPointReparseBuffer.PathBuffer, + reparseBuffer->MountPointReparseBuffer + .SubstituteNameLength>>1, &ds); + + copy = Tcl_DStringValue(&ds)+offset; + len = Tcl_DStringLength(&ds)-offset; + retVal = Tcl_NewStringObj(copy,len); + Tcl_IncrRefCount(retVal); + Tcl_DStringFree(&ds); + return retVal; + } + + invalidError: + Tcl_SetErrno(EINVAL); + return NULL; +} + +#if defined (__clang__) || ((__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) +#pragma GCC diagnostic pop +#endif + +/* + *-------------------------------------------------------------------- + * + * NativeReadReparse -- + * + * Read the junction/reparse information from a given NTFS directory. + * + * Assumption that linkDirPath is a valid, existing directory. + * + * Returns: + * Zero on success. + * + *-------------------------------------------------------------------- + */ + +static int +NativeReadReparse( + const WCHAR *linkDirPath, /* The junction to read */ + REPARSE_DATA_BUFFER *buffer,/* Pointer to buffer. Cannot be NULL */ + DWORD desiredAccess) +{ + HANDLE hFile; + DWORD returnedLength; + + hFile = CreateFileW(linkDirPath, desiredAccess, FILE_SHARE_READ, NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + + if (hFile == INVALID_HANDLE_VALUE) { + /* + * Error creating directory. + */ + + Tcl_WinConvertError(GetLastError()); + return -1; + } + + /* + * Get the link. + */ + + if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, + sizeof(DUMMY_REPARSE_BUFFER), &returnedLength, NULL)) { + /* + * Error setting junction. + */ + + Tcl_WinConvertError(GetLastError()); + CloseHandle(hFile); + return -1; + } + CloseHandle(hFile); + + if (!IsReparseTagValid(buffer->ReparseTag)) { + Tcl_SetErrno(EINVAL); + return -1; + } + return 0; +} + +/* + *-------------------------------------------------------------------- + * + * NativeWriteReparse -- + * + * Write the reparse information for a given directory. + * + * Assumption that LinkDirectory does not exist. + * + *-------------------------------------------------------------------- + */ + +static int +NativeWriteReparse( + const WCHAR *linkDirPath, + REPARSE_DATA_BUFFER *buffer) +{ + HANDLE hFile; + DWORD returnedLength; + + /* + * Create the directory - it must not already exist. + */ + + if (CreateDirectoryW(linkDirPath, NULL) == 0) { + /* + * Error creating directory. + */ + + Tcl_WinConvertError(GetLastError()); + return -1; + } + hFile = CreateFileW(linkDirPath, GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT + | FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + /* + * Error creating directory. + */ + + Tcl_WinConvertError(GetLastError()); + return -1; + } + + /* + * Set the link. + */ + + if (!DeviceIoControl(hFile, FSCTL_SET_REPARSE_POINT, buffer, + (DWORD) buffer->ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE, + NULL, 0, &returnedLength, NULL)) { + /* + * Error setting junction. + */ + + Tcl_WinConvertError(GetLastError()); + CloseHandle(hFile); + RemoveDirectoryW(linkDirPath); + return -1; + } + CloseHandle(hFile); + + /* + * We succeeded. + */ + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * tclWinDebugPanic -- + * + * Display a message. If a debugger is present, present it directly to + * the debugger, otherwise use a MessageBox. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +tclWinDebugPanic( + const char *format, ...) +{ +#define TCL_MAX_WARN_LEN 1024 + va_list argList; + char buf[TCL_MAX_WARN_LEN * 3]; + WCHAR msgString[TCL_MAX_WARN_LEN]; + + va_start(argList, format); + 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 screen + * and cause possible oversized window error. + */ + + if (msgString[TCL_MAX_WARN_LEN-1] != '\0') { + memcpy(msgString + (TCL_MAX_WARN_LEN - 5), L" ...", 5 * sizeof(WCHAR)); + } + if (IsDebuggerPresent()) { + OutputDebugStringW(msgString); + } else { + MessageBeep(MB_ICONEXCLAMATION); + MessageBoxW(NULL, msgString, L"Fatal Error", + MB_ICONSTOP | MB_OK | MB_TASKMODAL | MB_SETFOREGROUND); + } +} + +/* + *--------------------------------------------------------------------------- + * + * TclpFindExecutable -- + * + * This function computes the absolute path name of the current + * application. + * + * Results: + * None. + * + * Side effects: + * The computed path is stored. + * + *--------------------------------------------------------------------------- + */ + +void +TclpFindExecutable( + TCL_UNUSED(const char *)) +{ + WCHAR wName[MAX_PATH]; + char name[MAX_PATH * TCL_UTF_MAX]; + + GetModuleFileNameW(NULL, wName, sizeof(wName)/sizeof(wName[0])); + WideCharToMultiByte(CP_UTF8, 0, wName, -1, name, sizeof(name), NULL, NULL); + TclWinNoBackslash(name); + TclSetObjNameOfExecutable(Tcl_NewStringObj(name, TCL_INDEX_NONE), NULL); + +#if !defined(STATIC_BUILD) + HMODULE hModule = (HMODULE)TclWinGetTclInstance(); + if (hModule) { + GetModuleFileNameW(hModule, wName, sizeof(wName) / sizeof(wName[0])); + WideCharToMultiByte(CP_UTF8, 0, wName, -1, + name, sizeof(name), NULL, NULL); + TclSetObjNameOfShlib(Tcl_NewStringObj(name, TCL_AUTO_LENGTH), NULL); + } +#endif +} + +/* + *---------------------------------------------------------------------- + * + * TclpMatchInDirectory -- + * + * This routine is used by the globbing code to search a directory for + * all files which match a given pattern. + * + * Results: + * The return value is a standard Tcl result indicating whether an error + * occurred in globbing. Errors are left in interp, good results are + * lappended to resultPtr (which must be a valid object). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclpMatchInDirectory( + Tcl_Interp *interp, /* Interpreter to receive errors. */ + Tcl_Obj *resultPtr, /* List object to lappend results. */ + Tcl_Obj *pathPtr, /* Contains path to directory to search. */ + const char *pattern, /* Pattern to match against. */ + Tcl_GlobTypeData *types) /* Object containing list of acceptable types. + * May be NULL. In particular the directory + * flag is very important. */ +{ + const WCHAR *native; + + if (types != NULL && types->type == TCL_GLOB_TYPE_MOUNT) { + /* + * The native filesystem never adds mounts. + */ + + return TCL_OK; + } + + if (pattern == NULL || (*pattern == '\0')) { + Tcl_Obj *norm = Tcl_FSGetNormalizedPath(NULL, pathPtr); + + if (norm != NULL) { + /* + * Match a single file directly. + */ + + DWORD attr; + WIN32_FILE_ATTRIBUTE_DATA data; + Tcl_Size len = 0; + const char *str; + + if (norm != pathPtr) { Tcl_IncrRefCount(norm); } + str = TclGetStringFromObj(norm, &len); + native = (const WCHAR *)Tcl_FSGetNativePath(pathPtr); + + if (GetFileAttributesExW(native, + GetFileExInfoStandard, &data) != TRUE) { + if (norm != pathPtr) { Tcl_DecrRefCount(norm); } + return TCL_OK; + } + attr = data.dwFileAttributes; + + if (NativeMatchType(WinIsDrive(str, len), attr, native, types)) { + Tcl_ListObjAppendElement(interp, resultPtr, pathPtr); + } + if (norm != pathPtr) { Tcl_DecrRefCount(norm); } + } + return TCL_OK; + } else { + DWORD attr; + HANDLE handle; + WIN32_FIND_DATAW data; + const char *dirName; /* UTF-8 dir name, later with pattern + * appended. */ + Tcl_Size dirLength; + int matchSpecialDots; + Tcl_DString ds; /* Native encoding of dir, also used + * temporarily for other things. */ + Tcl_DString dsOrig; /* UTF-8 encoding of dir. */ + Tcl_Obj *fileNamePtr; + char lastChar; + + /* + * Get the normalized path representation (the main thing is we dont + * want any '~' sequences). + */ + + fileNamePtr = Tcl_FSGetNormalizedPath(interp, pathPtr); + if (fileNamePtr == NULL) { + return TCL_ERROR; + } + /* Ensure it'd be alive, while used. */ + if (fileNamePtr != pathPtr) { Tcl_IncrRefCount(fileNamePtr); } + + /* + * Verify that the specified path exists and is actually a directory. + */ + + native = (const WCHAR *)Tcl_FSGetNativePath(pathPtr); + if (native == NULL) { + if (fileNamePtr != pathPtr) { Tcl_DecrRefCount(fileNamePtr); } + return TCL_OK; + } + attr = GetFileAttributesW(native); + + if ((attr == INVALID_FILE_ATTRIBUTES) + || ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0)) { + if (fileNamePtr != pathPtr) { Tcl_DecrRefCount(fileNamePtr); } + return TCL_OK; + } + + /* + * Build up the directory name for searching, including a trailing + * directory separator. + */ + + Tcl_DStringInit(&dsOrig); + dirName = TclGetStringFromObj(fileNamePtr, &dirLength); + Tcl_DStringAppend(&dsOrig, dirName, dirLength); + + lastChar = dirName[dirLength -1]; + if ((lastChar != '\\') && (lastChar != '/') && (lastChar != ':')) { + TclDStringAppendLiteral(&dsOrig, "/"); + dirLength++; + } + if (fileNamePtr != pathPtr) { Tcl_DecrRefCount(fileNamePtr); } + dirName = Tcl_DStringValue(&dsOrig); + + /* + * We need to check all files in the directory, so we append '*.*' to + * the path, unless the pattern we've been given is rather simple, + * when we can use that instead. + */ + + if (strpbrk(pattern, "[]\\") == NULL) { + /* + * The pattern is a simple one containing just '*' and/or '?'. + * This means we can get the OS to help us, by passing it the + * pattern. + */ + + dirName = Tcl_DStringAppend(&dsOrig, pattern, TCL_INDEX_NONE); + } else { + dirName = TclDStringAppendLiteral(&dsOrig, "*.*"); + } + + Tcl_DStringInit(&ds); + native = Tcl_UtfToWCharDString(dirName, TCL_INDEX_NONE, &ds); + if ((types == NULL) || (types->type != TCL_GLOB_TYPE_DIR)) { + handle = FindFirstFileW(native, &data); + } else { + /* + * We can be more efficient, for pure directory requests. + */ + + handle = FindFirstFileExW(native, + FindExInfoStandard, &data, + FindExSearchLimitToDirectories, NULL, 0); + } + + if (handle == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + + Tcl_DStringFree(&ds); + if (IsNoSuchFileError(err)) { + /* + * We used our 'pattern' above, and matched nothing. This + * means we just return TCL_OK, indicating no results found. + */ + + Tcl_DStringFree(&dsOrig); + return TCL_OK; + } + + Tcl_WinConvertError(err); + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't read directory \"%s\": %s", + Tcl_DStringValue(&dsOrig), Tcl_PosixError(interp))); + } + Tcl_DStringFree(&dsOrig); + return TCL_ERROR; + } + Tcl_DStringFree(&ds); + + /* + * We may use this later, so we must restore it to its length + * including the directory delimiter. + */ + + Tcl_DStringSetLength(&dsOrig, dirLength); + + /* + * Check to see if the pattern should match the special . and + * .. names, referring to the current directory, or the directory + * above. We need a special check for this because paths beginning + * with a dot are not considered hidden on Windows, and so otherwise a + * relative glob like 'glob -join * *' will actually return + * './. ../..' etc. + */ + + if ((pattern[0] == '.') + || ((pattern[0] == '\\') && (pattern[1] == '.'))) { + matchSpecialDots = 1; + } else { + matchSpecialDots = 0; + } + + /* + * Now iterate over all of the files in the directory, starting with + * the first one we found. + */ + + do { + const char *utfname; + int checkDrive = 0, isDrive; + + native = data.cFileName; + attr = data.dwFileAttributes; + Tcl_DStringInit(&ds); + utfname = Tcl_WCharToUtfDString(native, TCL_INDEX_NONE, &ds); + + if (!matchSpecialDots) { + /* + * If it is exactly '.' or '..' then we ignore it. + */ + + if ((utfname[0] == '.') && (utfname[1] == '\0' + || (utfname[1] == '.' && utfname[2] == '\0'))) { + Tcl_DStringFree(&ds); + continue; + } + } else if (utfname[0] == '.' && utfname[1] == '.' + && utfname[2] == '\0') { + /* + * Have to check if this is a drive below, so we can correctly + * match 'hidden' and not hidden files. + */ + + checkDrive = 1; + } + + /* + * Check to see if the file matches the pattern. Note that we are + * ignoring the case sensitivity flag because Windows doesn't + * honor case even if the volume is case sensitive. If the volume + * also doesn't preserve case, then we previously returned the + * lower case form of the name. This didn't seem quite right since + * there are non-case-preserving volumes that actually return + * mixed case. So now we are returning exactly what we get from + * the system. + */ + + if (Tcl_StringCaseMatch(utfname, pattern, 1)) { + /* + * If the file matches, then we need to process the remainder + * of the path. + */ + + if (checkDrive) { + const char *fullname = Tcl_DStringAppend(&dsOrig, utfname, + Tcl_DStringLength(&ds)); + + isDrive = WinIsDrive(fullname, Tcl_DStringLength(&dsOrig)); + Tcl_DStringSetLength(&dsOrig, dirLength); + } else { + isDrive = 0; + } + if (NativeMatchType(isDrive, attr, native, types)) { + Tcl_ListObjAppendElement(interp, resultPtr, + TclNewFSPathObj(pathPtr, utfname, + Tcl_DStringLength(&ds))); + } + } + + /* + * Free ds here to ensure that native is valid above. + */ + + Tcl_DStringFree(&ds); + } while (FindNextFileW(handle, &data) == TRUE); + + FindClose(handle); + Tcl_DStringFree(&dsOrig); + return TCL_OK; + } +} + +/* + * Does the given path represent a root volume? We need this special case + * because for NTFS root volumes, the getFileAttributesProc returns a 'hidden' + * attribute when it should not. + */ + +static int +WinIsDrive( + const char *name, /* Name (UTF-8) */ + size_t len) /* Length of name */ +{ + int remove = 0; + + while (len > 4) { + if ((name[len-1] != '.' || name[len-2] != '.') + || (name[len-3] != '/' && name[len-3] != '\\')) { + /* + * We don't have '/..' at the end. + */ + + if (remove == 0) { + break; + } + remove--; + while (len > 0) { + len--; + if (name[len] == '/' || name[len] == '\\') { + break; + } + } + if (len < 4) { + len++; + break; + } + } else { + /* + * We do have '/..' + */ + + len -= 3; + remove++; + } + } + + if (len < 4) { + if (len == 0) { + /* + * Not sure if this is possible, but we pass it on anyway. + */ + } else if (len == 1 && (name[0] == '/' || name[0] == '\\')) { + /* + * Path is pointing to the root volume. + */ + + return 1; + } else if ((name[1] == ':') + && (len == 2 || (name[2] == '/' || name[2] == '\\'))) { + /* + * Path is of the form 'x:' or 'x:/' or 'x:\' + */ + + return 1; + } + } + + return 0; +} + +/* + * Does the given path represent a reserved window path name? If not return 0, + * if true, return the number of characters of the path that we actually want + * (not any trailing :). + */ + +static size_t +WinIsReserved( + const char *path) /* Path in UTF-8 */ +{ + if ((path[0] == 'c' || path[0] == 'C') + && (path[1] == 'o' || path[1] == 'O')) { + if ((path[2] == 'm' || path[2] == 'M') + && path[3] >= '1' && path[3] <= '9') { + /* + * May have match for 'com[1-9]:?', which is a serial port. + */ + + if (path[4] == '\0') { + return 4; + } else if (path[4] == ':' && path[5] == '\0') { + return 4; + } + } else if ((path[2] == 'n' || path[2] == 'N') && path[3] == '\0') { + /* + * Have match for 'con' + */ + + return 3; + } + + } else if ((path[0] == 'l' || path[0] == 'L') + && (path[1] == 'p' || path[1] == 'P') + && (path[2] == 't' || path[2] == 'T')) { + if (path[3] >= '1' && path[3] <= '9') { + /* + * May have match for 'lpt[1-9]:?' + */ + + if (path[4] == '\0') { + return 4; + } else if (path[4] == ':' && path[5] == '\0') { + return 4; + } + } + + } else if (!strcasecmp(path, "prn") || !strcasecmp(path, "nul") + || !strcasecmp(path, "aux")) { + /* + * Have match for 'prn', 'nul' or 'aux'. + */ + + return 3; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * NativeMatchType -- + * + * This function needs a special case for a path which is a root volume, + * because for NTFS root volumes, the getFileAttributesProc returns a + * 'hidden' attribute when it should not. + * + * We never make any calls to a 'get attributes' routine here, since we + * have arranged things so that our caller already knows such + * information. + * + * Results: + * 0 = file doesn't match + * 1 = file matches + * + *---------------------------------------------------------------------- + */ + +static int +NativeMatchType( + int isDrive, /* Is this a drive. */ + DWORD attr, /* We already know the attributes for the + * file. */ + const WCHAR *nativeName, /* Native path to check. */ + Tcl_GlobTypeData *types) /* Type description to match against. */ +{ + /* + * 'attr' represents the attributes of the file, but we only want to + * retrieve this info if it is absolutely necessary because it is an + * expensive call. Unfortunately, to deal with hidden files properly, we + * must always retrieve it. + */ + + if (types == NULL) { + /* + * If invisible, don't return the file. + */ + + return !(attr & FILE_ATTRIBUTE_HIDDEN && !isDrive); + } + + if (attr & FILE_ATTRIBUTE_HIDDEN && !isDrive) { + /* + * If invisible. + */ + + if ((types->perm == 0) || !(types->perm & TCL_GLOB_PERM_HIDDEN)) { + return 0; + } + } else { + /* + * Visible. + */ + + if (types->perm & TCL_GLOB_PERM_HIDDEN) { + return 0; + } + } + + if (types->perm != 0) { + if (((types->perm & TCL_GLOB_PERM_RONLY) && + !(attr & FILE_ATTRIBUTE_READONLY)) || + ((types->perm & TCL_GLOB_PERM_R) && + (0 /* File exists => R_OK on Windows */)) || + ((types->perm & TCL_GLOB_PERM_W) && + (attr & FILE_ATTRIBUTE_READONLY)) || + ((types->perm & TCL_GLOB_PERM_X) && + (!(attr & FILE_ATTRIBUTE_DIRECTORY) + && !NativeIsExec(nativeName)))) { + return 0; + } + } + + if ((types->type & TCL_GLOB_TYPE_DIR) + && (attr & FILE_ATTRIBUTE_DIRECTORY)) { + /* + * Quicker test for directory, which is a common case. + */ + + return 1; + + } else if (types->type != 0) { + unsigned short st_mode; + int isExec = NativeIsExec(nativeName); + + st_mode = NativeStatMode(attr, 0, isExec); + + /* + * In order bcdpfls as in 'find -t' + */ + + if (((types->type&TCL_GLOB_TYPE_BLOCK) && S_ISBLK(st_mode)) || + ((types->type&TCL_GLOB_TYPE_CHAR) && S_ISCHR(st_mode)) || + ((types->type&TCL_GLOB_TYPE_DIR) && S_ISDIR(st_mode)) || + ((types->type&TCL_GLOB_TYPE_PIPE) && S_ISFIFO(st_mode)) || +#ifdef S_ISSOCK + ((types->type&TCL_GLOB_TYPE_SOCK) && S_ISSOCK(st_mode)) || +#endif + ((types->type&TCL_GLOB_TYPE_FILE) && S_ISREG(st_mode))) { + /* + * Do nothing - this file is ok. + */ + } else { +#ifdef S_ISLNK + if (types->type & TCL_GLOB_TYPE_LINK) { + st_mode = NativeStatMode(attr, 1, isExec); + if (S_ISLNK(st_mode)) { + return 1; + } + } +#endif /* S_ISLNK */ + return 0; + } + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetUserHome -- + * + * This function takes the passed in user name and finds the + * corresponding home directory specified in the password file. + * + * Results: + * The result is a pointer to a string specifying the user's home + * directory, or NULL if the user's home directory could not be + * determined. Storage for the result string is allocated in bufferPtr; + * the caller must call Tcl_DStringFree() when the result is no longer + * needed. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +const char * +TclpGetUserHome( + const char *name, /* User name for desired home directory. */ + Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with + * name of user's home directory. */ +{ + char *result = NULL; + USER_INFO_1 *uiPtr; + Tcl_DString ds; + int nameLen = -1; + int rc = 0; + const char *domain; + WCHAR *wName, *wHomeDir, *wDomain; + + Tcl_DStringInit(bufferPtr); + + wDomain = NULL; + domain = Tcl_UtfFindFirst(name, '@'); + if (domain == NULL) { + const char *ptr; + + /* + * Treat the current user as a special case because the general case + * below does not properly retrieve the path. The NetUserGetInfo + * call returns an empty path and the code defaults to the user's + * name in the profiles directory. On modern Windows systems, this + * is generally wrong as when the account is a Microsoft account, + * for example abcdefghi@outlook.com, the directory name is + * abcde and not abcdefghi. + * + * Note we could have just used env(USERPROFILE) here but + * the intent is to retrieve (as on Unix) the system's view + * of the home irrespective of environment settings of HOME + * and USERPROFILE. + * + * Fixing this for the general user needs more investigating but + * at least for the current user we can use a direct call. + */ + ptr = TclpGetUserName(&ds); + if (ptr != NULL && strcasecmp(name, ptr) == 0) { + HANDLE hProcess; + WCHAR buf[MAX_PATH]; + DWORD nChars = sizeof(buf) / sizeof(buf[0]); + /* Sadly GetCurrentProcessToken not in Win 7 so slightly longer */ + hProcess = GetCurrentProcess(); /* Need not be closed */ + if (hProcess) { + HANDLE hToken; + if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { + if (GetUserProfileDirectoryW(hToken, buf, &nChars)) { + result = Tcl_WCharToUtfDString(buf, nChars-1, (bufferPtr)); + rc = 1; + } + CloseHandle(hToken); + } + } + } + Tcl_DStringFree(&ds); + } else { + Tcl_DStringInit(&ds); + wName = Tcl_UtfToWCharDString(domain + 1, TCL_INDEX_NONE, &ds); + rc = NetGetDCName(NULL, wName, (LPBYTE *) &wDomain); + Tcl_DStringFree(&ds); + nameLen = domain - name; + } + if (rc == 0) { + Tcl_DStringInit(&ds); + wName = Tcl_UtfToWCharDString(name, nameLen, &ds); + while (NetUserGetInfo(wDomain, wName, 1, (LPBYTE *) &uiPtr) != 0) { + /* + * User does not exist; if domain was not specified, try again + * using current domain. + */ + + rc = 1; + if (domain != NULL) { + break; + } + + /* + * Get current domain + */ + + rc = NetGetDCName(NULL, NULL, (LPBYTE *) &wDomain); + if (rc != 0) { + break; + } + domain = (const char *)INT2PTR(-1); /* repeat once */ + } + if (rc == 0) { + DWORD i, size = MAX_PATH; + + wHomeDir = uiPtr->usri1_home_dir; + if ((wHomeDir != NULL) && (wHomeDir[0] != '\0')) { + size = lstrlenW(wHomeDir); + Tcl_WCharToUtfDString(wHomeDir, size, bufferPtr); + } else { + WCHAR buf[MAX_PATH]; + /* + * User exists but has no home dir. Return + * "{GetProfilesDirectory}/". + */ + + GetProfilesDirectoryW(buf, &size); + Tcl_WCharToUtfDString(buf, size-1, bufferPtr); + Tcl_DStringAppend(bufferPtr, "/", 1); + Tcl_DStringAppend(bufferPtr, name, nameLen); + } + result = Tcl_DStringValue(bufferPtr); + + /* + * Be sure we return normalized path + */ + + for (i = 0; i < size; ++i) { + if (result[i] == '\\') { + result[i] = '/'; + } + } + NetApiBufferFree((void *)uiPtr); + } + Tcl_DStringFree(&ds); + } + if (wDomain != NULL) { + NetApiBufferFree((void *)wDomain); + } + + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * NativeAccess -- + * + * This function replaces the library version of access(), fixing the + * following bugs: + * + * 1. access() returns that all files have execute permission. + * + * Results: + * See access documentation. + * + * Side effects: + * See access documentation. + * + *--------------------------------------------------------------------------- + */ + +static int +NativeAccess( + const WCHAR *nativePath, /* Path of file to access, native encoding. */ + int mode) /* Permission setting. */ +{ + DWORD attr; + + attr = GetFileAttributesW(nativePath); + + if (attr == INVALID_FILE_ATTRIBUTES) { + /* + * File might not exist. + */ + + DWORD lasterror = GetLastError(); + if (lasterror != ERROR_SHARING_VIOLATION) { + Tcl_WinConvertError(lasterror); + return -1; + } + } + + if (mode == F_OK) { + /* + * File exists, nothing else to check. + */ + + return 0; + } + + /* + * If it's not a directory (assume file), do several fast checks: + */ + + if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) { + /* + * If the attributes say this is not writable at all. The file is a + * regular file (i.e., not a directory), then the file is not + * writable, full stop. For directories, the read-only bit is + * (mostly) ignored by Windows, so we can't ascertain anything about + * directory access from the attrib data. However, if we have the + * advanced 'getFileSecurityProc', then more robust ACL checks will be + * done below. + */ + + if ((mode & W_OK) && (attr & FILE_ATTRIBUTE_READONLY)) { + Tcl_SetErrno(EACCES); + return -1; + } + + /* + * If doesn't have the correct extension, it can't be executable + */ + + if ((mode & X_OK) && !NativeIsExec(nativePath)) { + Tcl_SetErrno(EACCES); + return -1; + } + + /* + * Special case for read/write/executable check on file + */ + + if ((mode & (R_OK|W_OK|X_OK)) && !(mode & ~(R_OK|W_OK|X_OK))) { + DWORD mask = 0; + HANDLE hFile; + + if (mode & R_OK) { + mask |= GENERIC_READ; + } + if (mode & W_OK) { + mask |= GENERIC_WRITE; + } + if (mode & X_OK) { + mask |= GENERIC_EXECUTE; + } + + hFile = CreateFileW(nativePath, mask, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); + return 0; + } + + /* + * Fast exit if access was denied + */ + + if (GetLastError() == ERROR_ACCESS_DENIED) { + Tcl_SetErrno(EACCES); + return -1; + } + } + + /* + * We cannot verify the access fast, check it below using security + * info. + */ + } + + /* + * It looks as if the permissions are ok, but if we are on NT, 2000 or XP, + * we have a more complex permissions structure so we try to check that. + * The code below is remarkably complex for such a simple thing as finding + * what permissions the OS has set for a file. + */ + + { + SECURITY_DESCRIPTOR *sdPtr = NULL; + unsigned long size; + PSID pSid = 0; + BOOL SidDefaulted; + SID_IDENTIFIER_AUTHORITY samba_unmapped = {{0, 0, 0, 0, 0, 22}}; + GENERIC_MAPPING genMap; + HANDLE hToken = NULL; + DWORD desiredAccess = 0, grantedAccess = 0; + BOOL accessYesNo = FALSE; + PRIVILEGE_SET privSet; + DWORD privSetSize = sizeof(PRIVILEGE_SET); + int error; + + /* + * First find out how big the buffer needs to be. + */ + + size = 0; + GetFileSecurityW(nativePath, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION + | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, + 0, 0, &size); + + /* + * Should have failed with ERROR_INSUFFICIENT_BUFFER + */ + + error = GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER) { + /* + * Most likely case is ERROR_ACCESS_DENIED, which we will convert + * to EACCES - just what we want! + */ + + Tcl_WinConvertError((DWORD) error); + return -1; + } + + /* + * Now size contains the size of buffer needed. + */ + + sdPtr = (SECURITY_DESCRIPTOR *) HeapAlloc(GetProcessHeap(), 0, size); + + if (sdPtr == NULL) { + goto accessError; + } + + /* + * Call GetFileSecurityW() for real. + */ + + if (!GetFileSecurityW(nativePath, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION + | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, + sdPtr, size, &size)) { + /* + * Error getting owner SD + */ + + goto accessError; + } + + /* + * As of Samba 3.0.23 (10-Jul-2006), unmapped users and groups are + * assigned to SID domains S-1-22-1 and S-1-22-2, where "22" is the + * top-level authority. If the file owner and group is unmapped then + * the ACL access check below will only test against world access, + * which is likely to be more restrictive than the actual access + * restrictions. Since the ACL tests are more likely wrong than + * right, skip them. Moreover, the unix owner access permissions are + * usually mapped to the Windows attributes, so if the user is the + * file owner then the attrib checks above are correct (as far as they + * go). + */ + + if(!GetSecurityDescriptorOwner(sdPtr,&pSid,&SidDefaulted) || + memcmp(GetSidIdentifierAuthority(pSid),&samba_unmapped, + sizeof(SID_IDENTIFIER_AUTHORITY))==0) { + HeapFree(GetProcessHeap(), 0, sdPtr); + return 0; /* Attrib tests say access allowed. */ + } + + /* + * Perform security impersonation of the user and open the resulting + * thread token. + */ + + if (!ImpersonateSelf(SecurityImpersonation)) { + /* + * Unable to perform security impersonation. + */ + + goto accessError; + } + if (!OpenThreadToken(GetCurrentThread(), + TOKEN_DUPLICATE | TOKEN_QUERY, FALSE, &hToken)) { + /* + * Unable to get current thread's token. + */ + + goto accessError; + } + + RevertToSelf(); + + /* + * Setup desiredAccess according to the access privileges we are + * checking. + */ + + if (mode & R_OK) { + desiredAccess |= FILE_GENERIC_READ; + } + if (mode & W_OK) { + desiredAccess |= FILE_GENERIC_WRITE; + } + if (mode & X_OK) { + desiredAccess |= FILE_GENERIC_EXECUTE; + } + + memset(&genMap, 0x0, sizeof(GENERIC_MAPPING)); + genMap.GenericRead = FILE_GENERIC_READ; + genMap.GenericWrite = FILE_GENERIC_WRITE; + genMap.GenericExecute = FILE_GENERIC_EXECUTE; + genMap.GenericAll = FILE_ALL_ACCESS; + + /* + * Perform access check using the token. + */ + + if (!AccessCheck(sdPtr, hToken, desiredAccess, + &genMap, &privSet, &privSetSize, &grantedAccess, + &accessYesNo)) { + /* + * Unable to perform access check. + */ + + accessError: + Tcl_WinConvertError(GetLastError()); + if (sdPtr != NULL) { + HeapFree(GetProcessHeap(), 0, sdPtr); + } + if (hToken != NULL) { + CloseHandle(hToken); + } + return -1; + } + + /* + * Clean up. + */ + + HeapFree(GetProcessHeap(), 0, sdPtr); + CloseHandle(hToken); + if (!accessYesNo) { + Tcl_SetErrno(EACCES); + return -1; + } + + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * NativeIsExec -- + * + * Determines if a path is executable. On windows this is simply defined + * by whether the path ends in a standard executable extension. + * + * Results: + * 1 = executable, 0 = not. + * + *---------------------------------------------------------------------- + */ + +static int +NativeIsExec( + const WCHAR *path) +{ + int len = wcslen(path); + + if (len < 5) { + return 0; + } + + if (path[len-4] != '.') { + return 0; + } + + path += len-3; + if ((_wcsicmp(path, L"exe") == 0) + || (_wcsicmp(path, L"com") == 0) + || (_wcsicmp(path, L"cmd") == 0) + || (_wcsicmp(path, L"bat") == 0)) { + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclpObjChdir -- + * + * This function replaces the library version of chdir(). + * + * Results: + * See chdir() documentation. + * + * Side effects: + * See chdir() documentation. + * + *---------------------------------------------------------------------- + */ + +int +TclpObjChdir( + Tcl_Obj *pathPtr) /* Path to new working directory. */ +{ + int result; + const WCHAR *nativePath; + + nativePath = (const WCHAR *)Tcl_FSGetNativePath(pathPtr); + + if (!nativePath) { + return -1; + } + result = SetCurrentDirectoryW(nativePath); + + if (result == 0) { + Tcl_WinConvertError(GetLastError()); + return -1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetCwd -- + * + * This function replaces the library version of getcwd(). (Obsolete + * function, only retained for old extensions which may call it + * directly). + * + * Results: + * The result is a pointer to a string specifying the current directory, + * or NULL if the current directory could not be determined. If NULL is + * returned, an error message is left in the interp's result. Storage for + * the result string is allocated in bufferPtr; the caller must call + * Tcl_DStringFree() when the result is no longer needed. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +const char * +TclpGetCwd( + Tcl_Interp *interp, /* If non-NULL, used for error reporting. */ + Tcl_DString *bufferPtr) /* Uninitialized or free DString filled with + * name of current directory. */ +{ + WCHAR buffer[MAX_PATH]; + char *p; + WCHAR *native; + + if (GetCurrentDirectoryW(MAX_PATH, buffer) == 0) { + Tcl_WinConvertError(GetLastError()); + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error getting working directory name: %s", + Tcl_PosixError(interp))); + } + return NULL; + } + + /* + * Watch for the weird Windows c:\\UNC syntax. + */ + + native = (WCHAR *) buffer; + if ((native[0] != '\0') && (native[1] == ':') + && (native[2] == '\\') && (native[3] == '\\')) { + native += 2; + } + Tcl_DStringInit(bufferPtr); + Tcl_WCharToUtfDString(native, TCL_INDEX_NONE, bufferPtr); + + /* + * Convert to forward slashes for easier use in scripts. + */ + + for (p = Tcl_DStringValue(bufferPtr); *p != '\0'; p++) { + if (*p == '\\') { + *p = '/'; + } + } + return Tcl_DStringValue(bufferPtr); +} + +int +TclpObjStat( + Tcl_Obj *pathPtr, /* Path of file to stat. */ + Tcl_StatBuf *statPtr) /* Filled with results of stat call. */ +{ + /* + * Ensure correct file sizes by forcing the OS to write any pending data + * to disk. This is done only for channels which are dirty, i.e. have been + * written to since the last flush here. + */ + + TclWinFlushDirtyChannels(); + + return NativeStat((const WCHAR *)Tcl_FSGetNativePath(pathPtr), statPtr, 0); +} + +/* + *---------------------------------------------------------------------- + * + * NativeStat -- + * + * This function replaces the library version of stat(), fixing the + * following bugs: + * + * 1. stat("c:") returns an error. + * 2. Borland stat() return time in GMT instead of localtime. + * 3. stat("\\server\mount") would return error. + * 4. Accepts slashes or backslashes. + * 5. st_dev and st_rdev were wrong for UNC paths. + * + * Results: + * See stat documentation. + * + * Side effects: + * See stat documentation. + * + *---------------------------------------------------------------------- + */ + +static int +NativeStat( + const WCHAR *nativePath, /* Path of file to stat */ + Tcl_StatBuf *statPtr, /* Filled with results of stat call. */ + int checkLinks) /* If non-zero, behave like 'lstat' */ +{ + DWORD attr; + int dev, nlink = 1; + unsigned short mode; + unsigned int inode = 0; + HANDLE fileHandle; + DWORD fileType = FILE_TYPE_UNKNOWN; + + /* + * If we can use 'createFile' on this, then we can use the resulting + * fileHandle to read more information (nlink, ino) than we can get from + * other attributes reading APIs. If not, then we try to fall back on the + * 'getFileAttributesExProc', and if that isn't available, then on even + * simpler routines. + * + * Special consideration must be given to Windows hard-coded names like + * CON, NULL, COM1, LPT1 etc. For these, we still need to do the + * CreateFile as some may not exist (e.g. there is no CON in wish by + * default). However the subsequent GetFileInformationByHandle will + * fail. We do a WinIsReserved to see if it is one of the special names, + * and if successful, mock up a BY_HANDLE_FILE_INFORMATION structure. + */ + + fileHandle = CreateFileW(nativePath, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); + + if (fileHandle != INVALID_HANDLE_VALUE) { + BY_HANDLE_FILE_INFORMATION data; + + if (GetFileInformationByHandle(fileHandle,&data) != TRUE) { + fileType = GetFileType(fileHandle); + CloseHandle(fileHandle); + if (fileType != FILE_TYPE_CHAR && fileType != FILE_TYPE_DISK) { + Tcl_SetErrno(ENOENT); + return -1; + } + + /* + * Mock up the expected structure + */ + + memset(&data, 0, sizeof(data)); + statPtr->st_atime = 0; + statPtr->st_mtime = 0; + statPtr->st_ctime = 0; + } else { + CloseHandle(fileHandle); + statPtr->st_atime = ToCTime(data.ftLastAccessTime); + statPtr->st_mtime = ToCTime(data.ftLastWriteTime); + statPtr->st_ctime = ToCTime(data.ftCreationTime); + } + attr = data.dwFileAttributes; + statPtr->st_size = ((long long) data.nFileSizeLow) | + (((long long) data.nFileSizeHigh) << 32); + + /* + * On Unix, for directories, nlink apparently depends on the number of + * files in the directory. We could calculate that, but it would be a + * bit of a performance penalty, I think. Hence we just use what + * Windows gives us, which is the same as Unix for files, at least. + */ + + nlink = data.nNumberOfLinks; + + /* + * Unfortunately our stat definition's inode field (unsigned short) + * will throw away most of the precision we have here, which means we + * can't rely on inode as a unique identifier of a file. We'd really + * like to do something like how we handle 'st_size'. + */ + + inode = data.nFileIndexHigh | data.nFileIndexLow; + } else { + /* + * Fall back on the less capable routines. This means no nlink or ino. + */ + + WIN32_FILE_ATTRIBUTE_DATA data; + + if (GetFileAttributesExW(nativePath, + GetFileExInfoStandard, &data) != TRUE) { + HANDLE hFind; + WIN32_FIND_DATAW ffd; + DWORD lasterror = GetLastError(); + + if (lasterror != ERROR_SHARING_VIOLATION) { + Tcl_WinConvertError(lasterror); + return -1; + } + hFind = FindFirstFileW(nativePath, &ffd); + if (hFind == INVALID_HANDLE_VALUE) { + Tcl_WinConvertError(GetLastError()); + return -1; + } + memcpy(&data, &ffd, sizeof(data)); + FindClose(hFind); + } + + attr = data.dwFileAttributes; + + statPtr->st_size = ((long long) data.nFileSizeLow) | + (((long long) data.nFileSizeHigh) << 32); + statPtr->st_atime = ToCTime(data.ftLastAccessTime); + statPtr->st_mtime = ToCTime(data.ftLastWriteTime); + statPtr->st_ctime = ToCTime(data.ftCreationTime); + } + + dev = NativeDev(nativePath); + mode = NativeStatMode(attr, checkLinks, NativeIsExec(nativePath)); + if (fileType == FILE_TYPE_CHAR) { + mode &= ~S_IFMT; + mode |= S_IFCHR; + } else if (fileType == FILE_TYPE_DISK) { + mode &= ~S_IFMT; + mode |= S_IFBLK; + } + + statPtr->st_dev = (dev_t) dev; + statPtr->st_ino = inode; + statPtr->st_mode = mode; + statPtr->st_nlink = nlink; + statPtr->st_uid = 0; + statPtr->st_gid = 0; + statPtr->st_rdev = (dev_t) dev; + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * NativeDev -- + * + * Calculate just the 'st_dev' field of a 'stat' structure. + * + *---------------------------------------------------------------------- + */ + +static int +NativeDev( + const WCHAR *nativePath) /* Full path of file to stat */ +{ + int dev; + Tcl_DString ds; + WCHAR nativeFullPath[MAX_PATH]; + WCHAR *nativePart; + const char *fullPath; + + GetFullPathNameW(nativePath, MAX_PATH, nativeFullPath, &nativePart); + Tcl_DStringInit(&ds); + fullPath = Tcl_WCharToUtfDString(nativeFullPath, TCL_INDEX_NONE, &ds); + + if ((fullPath[0] == '\\') && (fullPath[1] == '\\')) { + const char *p; + DWORD dw; + const WCHAR *nativeVol; + Tcl_DString volString; + + p = strchr(fullPath + 2, '\\'); + p = strchr(p + 1, '\\'); + if (p == NULL) { + /* + * Add terminating backslash to fullpath or GetVolumeInformationW() + * won't work. + */ + + fullPath = TclDStringAppendLiteral(&ds, "\\"); + p = fullPath + Tcl_DStringLength(&ds); + } else { + p++; + } + Tcl_DStringInit(&volString); + nativeVol = Tcl_UtfToWCharDString(fullPath, p - fullPath, &volString); + dw = (DWORD) -1; + GetVolumeInformationW(nativeVol, NULL, 0, &dw, NULL, NULL, NULL, 0); + + /* + * GetFullPathNameW() turns special devices like "NUL" into "\\.\NUL", + * but GetVolumeInformationW() returns failure for "\\.\NUL". This will + * cause "NUL" to get a drive number of -1, which makes about as much + * sense as anything since the special devices don't live on any + * drive. + */ + + dev = dw; + Tcl_DStringFree(&volString); + } else if ((fullPath[0] != '\0') && (fullPath[1] == ':')) { + dev = Tcl_UniCharToLower(fullPath[0]) - 'a'; + } else { + dev = -1; + } + Tcl_DStringFree(&ds); + + return dev; +} + +/* + *---------------------------------------------------------------------- + * + * NativeStatMode -- + * + * Calculate just the 'st_mode' field of a 'stat' structure. + * + * In many places we don't need the full stat structure, and it's much + * faster just to calculate these pieces, if that's all we need. + * + *---------------------------------------------------------------------- + */ + +static unsigned short +NativeStatMode( + DWORD attr, + int checkLinks, + int isExec) +{ + int mode; + + if (checkLinks && (attr & FILE_ATTRIBUTE_REPARSE_POINT)) { + /* + * It is a link. + */ + + mode = S_IFLNK; + } else { + mode = (attr & FILE_ATTRIBUTE_DIRECTORY) ? S_IFDIR|S_IEXEC : S_IFREG; + } + mode |= (attr & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD|S_IWRITE; + if (isExec) { + mode |= S_IEXEC; + } + + /* + * Propagate the S_IREAD, S_IWRITE, S_IEXEC bits to the group and other + * positions. + */ + + mode |= (mode & (S_IREAD|S_IWRITE|S_IEXEC)) >> 3; + mode |= (mode & (S_IREAD|S_IWRITE|S_IEXEC)) >> 6; + return (unsigned short) mode; +} + +/* + *------------------------------------------------------------------------ + * + * ToCTime -- + * + * Converts a Windows FILETIME to a __time64_t in UTC. + * + * Results: + * Returns the count of seconds from the Posix epoch. + * + *------------------------------------------------------------------------ + */ + +static __time64_t +ToCTime( + FILETIME fileTime) /* UTC time */ +{ + LARGE_INTEGER convertedTime; + + convertedTime.LowPart = fileTime.dwLowDateTime; + convertedTime.HighPart = (LONG) fileTime.dwHighDateTime; + + return (__time64_t) ((convertedTime.QuadPart - + POSIX_EPOCH_AS_FILETIME) / 10000000); +} + +/* + *------------------------------------------------------------------------ + * + * FromCTime -- + * + * Converts a __time64_t to a Windows FILETIME + * + * Results: + * Returns the count of 100-ns ticks seconds from the Windows epoch. + * + *------------------------------------------------------------------------ + */ + +static void +FromCTime( + __time64_t posixTime, + FILETIME *fileTime) /* UTC Time */ +{ + LARGE_INTEGER convertedTime; + + convertedTime.QuadPart = ((LONGLONG) posixTime) * 10000000 + + POSIX_EPOCH_AS_FILETIME; + fileTime->dwLowDateTime = convertedTime.LowPart; + fileTime->dwHighDateTime = convertedTime.HighPart; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpGetNativeCwd -- + * + * This function replaces the library version of getcwd(). + * + * Results: + * The input and output are filesystem paths in native form. The result + * is either the given clientData, if the working directory hasn't + * changed, or a new clientData (owned by our caller), giving the new + * native path, or NULL if the current directory could not be determined. + * If NULL is returned, the caller can examine the standard Posix error + * codes to determine the cause of the problem. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void * +TclpGetNativeCwd( + void *clientData) +{ + WCHAR buffer[MAX_PATH]; + + if (GetCurrentDirectoryW(MAX_PATH, buffer) == 0) { + Tcl_WinConvertError(GetLastError()); + return NULL; + } + + if (clientData != NULL) { + if (wcscmp((const WCHAR *) clientData, buffer) == 0) { + return clientData; + } + } + + return TclNativeDupInternalRep(buffer); +} + +int +TclpObjAccess( + Tcl_Obj *pathPtr, + int mode) +{ + return NativeAccess((const WCHAR *)Tcl_FSGetNativePath(pathPtr), mode); +} + +int +TclpObjLstat( + Tcl_Obj *pathPtr, + Tcl_StatBuf *statPtr) +{ + /* + * Ensure correct file sizes by forcing the OS to write any pending data + * to disk. This is done only for channels which are dirty, i.e. have been + * written to since the last flush here. + */ + + TclWinFlushDirtyChannels(); + + return NativeStat((const WCHAR *)Tcl_FSGetNativePath(pathPtr), statPtr, 1); +} + +#ifdef S_IFLNK +Tcl_Obj * +TclpObjLink( + Tcl_Obj *pathPtr, + Tcl_Obj *toPtr, + int linkAction) +{ + if (toPtr != NULL) { + int res; + const WCHAR *LinkTarget; + const WCHAR *LinkSource = (const WCHAR *)Tcl_FSGetNativePath(pathPtr); + Tcl_Obj *normToPtr = Tcl_FSGetNormalizedPath(NULL, toPtr); + + if (normToPtr == NULL) { + return NULL; + } + if (normToPtr != toPtr) { Tcl_IncrRefCount(normToPtr); } + + LinkTarget = (const WCHAR *)Tcl_FSGetNativePath(normToPtr); + + if (LinkSource == NULL || LinkTarget == NULL) { + if (normToPtr != toPtr) { Tcl_DecrRefCount(normToPtr); } + return NULL; + } + res = WinLink(LinkSource, LinkTarget, linkAction); + if (normToPtr != toPtr) { Tcl_DecrRefCount(normToPtr); } + if (res == 0) { + return toPtr; + } else { + return NULL; + } + } else { + const WCHAR *LinkSource = (const WCHAR *)Tcl_FSGetNativePath(pathPtr); + + if (LinkSource == NULL) { + return NULL; + } + return WinReadLink(LinkSource); + } +} +#endif /* S_IFLNK */ + +/* + *--------------------------------------------------------------------------- + * + * TclpFilesystemPathType -- + * + * This function is part of the native filesystem support, and returns + * the path type of the given path. Returns NTFS or FAT or whatever is + * returned by the 'volume information' proc. + * + * Results: + * NULL at present. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpFilesystemPathType( + Tcl_Obj *pathPtr) +{ +#define VOL_BUF_SIZE 32 + int found; + WCHAR volType[VOL_BUF_SIZE]; + char *firstSeparator; + const char *path; + Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr); + + if (normPath == NULL) { + return NULL; + } + path = TclGetString(normPath); + if (path == NULL) { + return NULL; + } + + firstSeparator = strchr((char *)path, '/'); + if (firstSeparator == NULL) { + found = GetVolumeInformationW((const WCHAR *)Tcl_FSGetNativePath(pathPtr), + NULL, 0, NULL, NULL, NULL, volType, VOL_BUF_SIZE); + } else { + Tcl_Obj *driveName = Tcl_NewStringObj(path, firstSeparator - path+1); + + Tcl_IncrRefCount(driveName); + found = GetVolumeInformationW((const WCHAR *)Tcl_FSGetNativePath(driveName), + NULL, 0, NULL, NULL, NULL, volType, VOL_BUF_SIZE); + Tcl_DecrRefCount(driveName); + } + + if (found == 0) { + return NULL; + } else { + Tcl_DString ds; + + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString(volType, TCL_INDEX_NONE, &ds); + return Tcl_DStringToObj(&ds); + } +#undef VOL_BUF_SIZE +} + +/* + * This define can be turned on to experiment with a different way of + * normalizing paths (using a different Windows API). Unfortunately the new + * path seems to take almost exactly the same amount of time as the old path! + * The primary time taken by normalization is in + * GetFileAttributesEx/FindFirstFile or GetFileAttributesEx/GetLongPathName. + * Conversion to/from native is not a significant factor at all. + * + * Also, since we have to check for symbolic links (reparse points) then we + * have to call GetFileAttributes on each path segment anyway, so there's no + * benefit to doing anything clever there. + */ + +/* #define TclNORM_LONG_PATH */ + +/* + *--------------------------------------------------------------------------- + * + * TclpObjNormalizePath -- + * + * This function scans through a path specification and replaces it, in + * place, with a normalized version. This means using the 'longname', and + * expanding any symbolic links contained within the path. + * + * Results: + * The new 'nextCheckpoint' value, giving as far as we could understand + * in the path. + * + * Side effects: + * The pathPtr string, which must contain a valid path, is possibly + * modified in place. + * + *--------------------------------------------------------------------------- + */ + +int +TclpObjNormalizePath( + TCL_UNUSED(Tcl_Interp *), + Tcl_Obj *pathPtr, /* An unshared object containing the path to + * normalize */ + int nextCheckpoint) /* offset to start at in pathPtr */ +{ + char *lastValidPathEnd = NULL; + Tcl_DString dsNorm; /* This will hold the normalized string. */ + char *path, *currentPathEndPosition; + Tcl_Obj *temp = NULL; + int isDrive = 1; + Tcl_DString ds; /* Some workspace. */ + + Tcl_DStringInit(&dsNorm); + path = TclGetString(pathPtr); + + currentPathEndPosition = path + nextCheckpoint; + if (*currentPathEndPosition == '/') { + currentPathEndPosition++; + } + while (1) { + char cur = *currentPathEndPosition; + + if ((cur=='/' || cur==0) && (path != currentPathEndPosition)) { + /* + * Reached directory separator, or end of string. + */ + + WIN32_FILE_ATTRIBUTE_DATA data; + const WCHAR *nativePath; + + Tcl_DStringInit(&ds); + nativePath = Tcl_UtfToWCharDString(path, + currentPathEndPosition - path, &ds); + + if (GetFileAttributesExW(nativePath, + GetFileExInfoStandard, &data) != TRUE) { + /* + * File doesn't exist. + */ + + if (isDrive) { + size_t len = WinIsReserved(path); + + if (len > 0) { + /* + * Actually it does exist - COM1, etc. + */ + + size_t i; + + for (i=0 ; i= 'a') { + wc -= ('a' - 'A'); + ((WCHAR *) nativePath)[i] = wc; + } + } + Tcl_DStringAppend(&dsNorm, + (const char *)nativePath, + sizeof(WCHAR) * len); + lastValidPathEnd = currentPathEndPosition; + } else if (nextCheckpoint == 0) { + /* + * Path starts with a drive designation that's not + * actually on the system. We still must normalize up + * past the first separator. [Bug 3603434] + */ + + currentPathEndPosition++; + } + } + Tcl_DStringFree(&ds); + break; + } + + /* + * File 'nativePath' does exist if we get here. We now want to + * check if it is a symlink and otherwise continue with the + * rest of the path. + */ + + /* + * Check for symlinks, except at last component of path (we don't + * follow final symlinks). Also a drive (C:/) for example, may + * sometimes have the reparse flag set for some reason I don't + * understand. We therefore don't perform this check for drives. + */ + + if (cur != 0 && !isDrive && + data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT){ + Tcl_Obj *to = WinReadLinkDirectory(nativePath); + + if (to != NULL) { + /* + * Read the reparse point ok. Now, reparse points need not + * be normalized, otherwise we could use: + * + * Tcl_GetStringFromObj(to, &pathLen); + * nextCheckpoint = pathLen; + * + * So, instead we have to start from the beginning. + */ + + nextCheckpoint = 0; + Tcl_AppendToObj(to, currentPathEndPosition, TCL_INDEX_NONE); + + /* + * Convert link to forward slashes. + */ + + for (path = TclGetString(to); *path != 0; path++) { + if (*path == '\\') { + *path = '/'; + } + } + path = TclGetString(to); + currentPathEndPosition = path + nextCheckpoint; + if (temp != NULL) { + Tcl_DecrRefCount(temp); + } + temp = to; + + /* + * Reset variables so we can restart normalization. + */ + + isDrive = 1; + Tcl_DStringFree(&dsNorm); + Tcl_DStringFree(&ds); + continue; + } + } + +#ifndef TclNORM_LONG_PATH + /* + * Now we convert the tail of the current path to its 'long form', + * and append it to 'dsNorm' which holds the current normalized + * path + */ + + if (isDrive) { + WCHAR drive = ((WCHAR *) nativePath)[0]; + + if (drive >= 'a') { + drive -= ('a' - 'A'); + ((WCHAR *) nativePath)[0] = drive; + } + Tcl_DStringAppend(&dsNorm, (const char *)nativePath, + Tcl_DStringLength(&ds)); + } else { + char *checkDots = NULL; + + if (lastValidPathEnd[1] == '.') { + checkDots = lastValidPathEnd + 1; + while (checkDots < currentPathEndPosition) { + if (*checkDots != '.') { + checkDots = NULL; + break; + } + checkDots++; + } + } + if (checkDots != NULL) { + int dotLen = currentPathEndPosition-lastValidPathEnd; + + /* + * Path is just dots. We shouldn't really ever see a path + * like that. However, to be nice we at least don't mangle + * the path - we just add the dots as a path segment and + * continue. + */ + + Tcl_DStringAppend(&dsNorm, ((const char *)nativePath) + + Tcl_DStringLength(&ds) + - (dotLen * sizeof(WCHAR)), + dotLen * sizeof(WCHAR)); + } else { + /* + * Normal path. + */ + + WIN32_FIND_DATAW fData; + HANDLE handle; + + handle = FindFirstFileW((WCHAR *) nativePath, &fData); + if (handle == INVALID_HANDLE_VALUE) { + /* + * This is usually the '/' in 'c:/' at end of string. + */ + + Tcl_DStringAppend(&dsNorm, (const char *) L"/", + sizeof(WCHAR)); + } else { + WCHAR *nativeName; + + if (fData.cFileName[0] != '\0') { + nativeName = fData.cFileName; + } else { + nativeName = fData.cAlternateFileName; + } + FindClose(handle); + Tcl_DStringAppend(&dsNorm, (const char *) L"/", + sizeof(WCHAR)); + Tcl_DStringAppend(&dsNorm, + (const char *) nativeName, + wcslen(nativeName)*sizeof(WCHAR)); + } + } + } +#endif /* !TclNORM_LONG_PATH */ + Tcl_DStringFree(&ds); + lastValidPathEnd = currentPathEndPosition; + if (cur == 0) { + break; + } + + /* + * If we get here, we've got past one directory delimiter, so we + * know it is no longer a drive. + */ + + isDrive = 0; + } + currentPathEndPosition++; + +#ifdef TclNORM_LONG_PATH + /* + * Convert the entire known path to long form. + */ + + WCHAR wpath[MAX_PATH]; + const WCHAR *nativePath; + DWORD wpathlen; + + Tcl_DStringInit(&ds); + nativePath = + Tcl_UtfToWCharDString(path, lastValidPathEnd - path, &ds); + wpathlen = GetLongPathNameProc(nativePath, + (WCHAR *) wpath, MAX_PATH); + /* + * We have to make the drive letter uppercase. + */ + + if (wpath[0] >= 'a') { + wpath[0] -= ('a' - 'A'); + } + Tcl_DStringAppend(&dsNorm, (const char *) wpath, + wpathlen * sizeof(WCHAR)); + Tcl_DStringFree(&ds); +#endif /* TclNORM_LONG_PATH */ + } + + /* + * Common code path for all Windows platforms. + */ + + nextCheckpoint = currentPathEndPosition - path; + if (lastValidPathEnd != NULL) { + /* + * Concatenate the normalized string in dsNorm with the tail of the + * path which we didn't recognise. The string in dsNorm is in the + * native encoding, so we have to convert it to Utf. + */ + + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString((const WCHAR *) Tcl_DStringValue(&dsNorm), + Tcl_DStringLength(&dsNorm)>>1, &ds); + nextCheckpoint = Tcl_DStringLength(&ds); + if (*lastValidPathEnd != 0) { + /* + * Not the end of the string. + */ + + Tcl_Obj *tmpPathPtr; + Tcl_Size len; + + tmpPathPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), + nextCheckpoint); + Tcl_AppendToObj(tmpPathPtr, lastValidPathEnd, TCL_INDEX_NONE); + path = TclGetStringFromObj(tmpPathPtr, &len); + Tcl_SetStringObj(pathPtr, path, len); + Tcl_DecrRefCount(tmpPathPtr); + } else { + /* + * End of string was reached above. + */ + + Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds), nextCheckpoint); + } + Tcl_DStringFree(&ds); + } + Tcl_DStringFree(&dsNorm); + + /* + * This must be done after we are totally finished with 'path' as we are + * sharing the same underlying string. + */ + + if (temp != NULL) { + Tcl_DecrRefCount(temp); + } + + return nextCheckpoint; +} + +/* + *--------------------------------------------------------------------------- + * + * TclWinVolumeRelativeNormalize -- + * + * Only Windows has volume-relative paths. These paths are rather rare, + * but it is nice if Tcl can handle them. It is much better if we can + * handle them here, rather than in the native fs code, because we really + * need to have a real absolute path just below. + * + * We do not let this block compile on non-Windows platforms because the + * test suite's manual forcing of tclPlatform can otherwise cause this + * code path to be executed, causing various errors because + * volume-relative paths really do not exist. + * + * Results: + * A valid normalized path. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +Tcl_Obj * +TclWinVolumeRelativeNormalize( + Tcl_Interp *interp, + const char *path, + Tcl_Obj **useThisCwdPtr) +{ + Tcl_Obj *absolutePath, *useThisCwd; + + useThisCwd = Tcl_FSGetCwd(interp); + if (useThisCwd == NULL) { + return NULL; + } + + if (path[0] == '/') { + /* + * Path of form /foo/bar which is a path in the root directory of the + * current volume. + */ + + const char *drive = TclGetString(useThisCwd); + + absolutePath = Tcl_NewStringObj(drive,2); + Tcl_AppendToObj(absolutePath, path, TCL_INDEX_NONE); + Tcl_IncrRefCount(absolutePath); + + /* + * We have a refCount on the cwd. + */ + } else { + /* + * Path of form C:foo/bar, but this only makes sense if the cwd is + * also on drive C. + */ + + Tcl_Size cwdLen; + const char *drive = TclGetStringFromObj(useThisCwd, &cwdLen); + char drive_cur = path[0]; + + if (drive_cur >= 'a') { + drive_cur -= ('a' - 'A'); + } + if (drive[0] == drive_cur) { + absolutePath = Tcl_DuplicateObj(useThisCwd); + + /* + * We have a refCount on the cwd, which we will release later. + */ + + if (drive[cwdLen-1] != '/' && (path[2] != '\0')) { + /* + * Only add a trailing '/' if needed, which is if there isn't + * one already, and if we are going to be adding some more + * characters. + */ + + Tcl_AppendToObj(absolutePath, "/", 1); + } + } else { + Tcl_DecrRefCount(useThisCwd); + useThisCwd = NULL; + + /* + * The path is not in the current drive, but is volume-relative. + * The way Tcl 8.3 handles this is that it treats such a path as + * relative to the root of the drive. We therefore behave the same + * here. This behaviour is, however, different to that of the + * windows command-line. If we want to fix this at some point in + * the future (at the expense of a behaviour change to Tcl), we + * could use the '_dgetdcwd' Win32 API to get the drive's cwd. + */ + + absolutePath = Tcl_NewStringObj(path, 2); + Tcl_AppendToObj(absolutePath, "/", 1); + } + Tcl_IncrRefCount(absolutePath); + Tcl_AppendToObj(absolutePath, path+2, TCL_INDEX_NONE); + } + *useThisCwdPtr = useThisCwd; + return absolutePath; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpNativeToNormalized -- + * + * Convert native format to a normalized path object, with refCount of + * zero. + * + * Currently assumes all native paths are actually normalized already, so + * if the path given is not normalized this will actually just convert to + * a valid string path, but not necessarily a normalized one. + * + * Results: + * A valid normalized path. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpNativeToNormalized( + void *clientData) +{ + Tcl_DString ds; + Tcl_Obj *objPtr; + size_t len; + char *copy, *p; + + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString((const WCHAR *) clientData, TCL_INDEX_NONE, &ds); + copy = Tcl_DStringValue(&ds); + len = Tcl_DStringLength(&ds); + + /* + * Certain native path representations on Windows have this special prefix + * to indicate that they are to be treated specially. For example + * extremely long paths, or symlinks. + */ + + if (*copy == '\\') { + if (0 == strncmp(copy,"\\??\\",4)) { + copy += 4; + len -= 4; + } else if (0 == strncmp(copy,"\\\\?\\",4)) { + copy += 4; + len -= 4; + } + } + + /* + * Ensure we are using forward slashes only. + */ + + for (p = copy; *p != '\0'; p++) { + if (*p == '\\') { + *p = '/'; + } + } + + objPtr = Tcl_NewStringObj(copy,len); + Tcl_DStringFree(&ds); + + return objPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * TclNativeCreateNativeRep -- + * + * Create a native representation for the given path. + * + * Results: + * The nativePath representation. + * + * Side effects: + * Memory will be allocated. The path might be normalized. + * + *--------------------------------------------------------------------------- + */ + +void * +TclNativeCreateNativeRep( + Tcl_Obj *pathPtr) +{ + WCHAR *nativePathPtr = NULL; + const char *str; + Tcl_Obj *validPathPtr; + Tcl_Size len; + WCHAR *wp; + + if (TclFSCwdIsNative() || Tcl_FSGetPathType(pathPtr) == TCL_PATH_ABSOLUTE) { + /* + * The cwd is native (or path is absolute), use the translated path + * without worrying about normalization (this will also usually be + * shorter so the utf-to-external conversion will be somewhat faster). + */ + + validPathPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr); + if (validPathPtr == NULL) { + return NULL; + } + + /* + * refCount of validPathPtr was already incremented in + * Tcl_FSGetTranslatedPath + */ + } else { + /* + * Make sure the normalized path is set. + */ + + validPathPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr); + if (validPathPtr == NULL) { + return NULL; + } + + /* + * validPathPtr returned from Tcl_FSGetNormalizedPath is owned by Tcl, + * so incr refCount here + */ + + Tcl_IncrRefCount(validPathPtr); + } + + str = TclGetStringFromObj(validPathPtr, &len); + + if (strlen(str) != (size_t)len) { + /* + * String contains NUL-bytes. This is invalid. + */ + + goto done; + } + + /* + * For a reserved device, strip a possible postfix ':' + */ + + len = WinIsReserved(str); + if (len == 0) { + /* + * Let MultiByteToWideChar check for other invalid sequences, like + * 0xC0 0x80 (== overlong NUL). See bug [3118489]: NUL in filenames + */ + + len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, 0, 0); + if (len==0) { + goto done; + } + } + + /* + * Overallocate 6 chars, making some room for extended paths + */ + + wp = nativePathPtr = (WCHAR *)Tcl_Alloc((len + 6) * sizeof(WCHAR)); + if (nativePathPtr==0) { + goto done; + } + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str, -1, nativePathPtr, + len + 2); + nativePathPtr[len] = 0; + + /* + * If path starts with "//?/" or "\\?\" (extended path), translate any + * slashes to backslashes but leave the '?' intact + */ + + if ((str[0] == '\\' || str[0] == '/') && (str[1] == '\\' || str[1] == '/') + && str[2] == '?' && (str[3] == '\\' || str[3] == '/')) { + wp[0] = wp[1] = wp[3] = '\\'; + str += 4; + wp += 4; + } + + /* + * If there is no "\\?\" prefix but there is a drive or UNC path prefix + * and the path is larger than MAX_PATH chars, no Win32 API function can + * handle that unless it is prefixed with the extended path prefix. See: + * + */ + + if (((str[0] >= 'A' && str[0] <= 'Z') || (str[0] >= 'a' && str[0] <= 'z')) + && str[1] == ':') { + if (wp == nativePathPtr && len > MAX_PATH + && (str[2] == '\\' || str[2] == '/')) { + memmove(wp + 4, wp, len * sizeof(WCHAR)); + memcpy(wp, L"\\\\?\\", 4 * sizeof(WCHAR)); + wp += 4; + } + + /* + * If (remainder of) path starts with ":", leave the ':' + * intact. + */ + + wp += 2; + } else if (wp == nativePathPtr && len > MAX_PATH + && (str[0] == '\\' || str[0] == '/') + && (str[1] == '\\' || str[1] == '/') && str[2] != '?') { + memmove(wp + 6, wp, len * sizeof(WCHAR)); + memcpy(wp, L"\\\\?\\UNC", 7 * sizeof(WCHAR)); + wp += 7; + } + + /* + * In the remainder of the path, translate invalid characters to + * characters in the Unicode private use area. + */ + + while (*wp != '\0') { + if ((*wp < ' ') || wcschr(L"\"*<>?|", *wp)) { + *wp |= 0xF000; + } else if (*wp == '/') { + *wp = '\\'; + } + ++wp; + } + + done: + TclDecrRefCount(validPathPtr); + return nativePathPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * TclNativeDupInternalRep -- + * + * Duplicate the native representation. + * + * Results: + * The copied native representation, or NULL if it is not possible to + * copy the representation. + * + * Side effects: + * Memory allocation for the copy. + * + *--------------------------------------------------------------------------- + */ + +void * +TclNativeDupInternalRep( + void *clientData) +{ + char *copy; + size_t len; + + if (clientData == NULL) { + return NULL; + } + + len = sizeof(WCHAR) * (wcslen((const WCHAR *) clientData) + 1); + + copy = (char *)Tcl_Alloc(len); + memcpy(copy, clientData, len); + return copy; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpUtime -- + * + * Set the modification date for a file. + * + * Results: + * 0 on success, -1 on error. + * + * Side effects: + * Sets errno to a representation of any Windows problem that's observed + * in the process. + * + *--------------------------------------------------------------------------- + */ + +int +TclpUtime( + Tcl_Obj *pathPtr, /* File to modify */ + struct utimbuf *tval) /* New modification date structure */ +{ + int res = 0; + HANDLE fileHandle; + const WCHAR *native; + DWORD attr = 0; + DWORD flags = FILE_ATTRIBUTE_NORMAL; + FILETIME lastAccessTime, lastModTime; + + FromCTime(tval->actime, &lastAccessTime); + FromCTime(tval->modtime, &lastModTime); + + native = (const WCHAR *)Tcl_FSGetNativePath(pathPtr); + + attr = GetFileAttributesW(native); + + if (attr != INVALID_FILE_ATTRIBUTES && attr & FILE_ATTRIBUTE_DIRECTORY) { + flags = FILE_FLAG_BACKUP_SEMANTICS; + } + + /* + * We use the native APIs (not 'utime') because there are some daylight + * savings complications that utime gets wrong. + */ + + fileHandle = CreateFileW(native, FILE_WRITE_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, flags, NULL); + + if (fileHandle == INVALID_HANDLE_VALUE || + !SetFileTime(fileHandle, NULL, &lastAccessTime, &lastModTime)) { + Tcl_WinConvertError(GetLastError()); + res = -1; + } + if (fileHandle != INVALID_HANDLE_VALUE) { + CloseHandle(fileHandle); + } + return res; +} + +/* + *--------------------------------------------------------------------------- + * + * TclWinFileOwned -- + * + * Returns 1 if the specified file exists and is owned by the current + * user and 0 otherwise. Like the Unix case, the check is made using + * the real process SID, not the effective (impersonation) one. + * + *--------------------------------------------------------------------------- + */ + +int +TclWinFileOwned( + Tcl_Obj *pathPtr) /* File whose ownership is to be checked */ +{ + const WCHAR *native; + PSID ownerSid = NULL; + PSECURITY_DESCRIPTOR secd = NULL; + HANDLE token; + LPBYTE buf = NULL; + DWORD bufsz; + int owned = 0; + + native = (const WCHAR *)Tcl_FSGetNativePath(pathPtr); + + if (GetNamedSecurityInfoW((LPWSTR) native, SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION, &ownerSid, NULL, NULL, NULL, + &secd) != ERROR_SUCCESS) { + /* + * Either not a file, or we do not have access to it in which case we + * are in all likelihood not the owner. + */ + + return 0; + } + + /* + * Getting the current process SID is a multi-step process. We make the + * assumption that if a call fails, this process is so underprivileged it + * could not possibly own anything. Normally a process can *always* look + * up its own token. + */ + + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + /* + * Find out how big the buffer needs to be. + */ + + bufsz = 0; + GetTokenInformation(token, TokenUser, NULL, 0, &bufsz); + if (bufsz) { + buf = (LPBYTE)Tcl_Alloc(bufsz); + if (GetTokenInformation(token, TokenUser, buf, bufsz, &bufsz)) { + owned = EqualSid(ownerSid, ((PTOKEN_USER) buf)->User.Sid); + } + } + CloseHandle(token); + } + + /* + * Free allocations and be done. + */ + + if (secd) { + LocalFree(secd); /* Also frees ownerSid */ + } + if (buf) { + Tcl_Free(buf); + } + + return (owned != 0); /* Convert non-0 to 1 */ +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclWinInit.c b/vendor/tcl/win/tclWinInit.c new file mode 100644 index 00000000..8d7610df --- /dev/null +++ b/vendor/tcl/win/tclWinInit.c @@ -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 +#include +#include +#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: + */ diff --git a/vendor/tcl/win/tclWinInt.h b/vendor/tcl/win/tclWinInt.h new file mode 100644 index 00000000..c37daf73 --- /dev/null +++ b/vendor/tcl/win/tclWinInt.h @@ -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 */ diff --git a/vendor/tcl/win/tclWinLoad.c b/vendor/tcl/win/tclWinLoad.c new file mode 100644 index 00000000..286a212c --- /dev/null +++ b/vendor/tcl/win/tclWinLoad.c @@ -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: + */ diff --git a/vendor/tcl/win/tclWinNotify.c b/vendor/tcl/win/tclWinNotify.c new file mode 100644 index 00000000..2c93a416 --- /dev/null +++ b/vendor/tcl/win/tclWinNotify.c @@ -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: + */ diff --git a/vendor/tcl/win/tclWinPanic.c b/vendor/tcl/win/tclWinPanic.c new file mode 100644 index 00000000..3d9e98c7 --- /dev/null +++ b/vendor/tcl/win/tclWinPanic.c @@ -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: + */ diff --git a/vendor/tcl/win/tclWinPipe.c b/vendor/tcl/win/tclWinPipe.c new file mode 100644 index 00000000..d3393ea6 --- /dev/null +++ b/vendor/tcl/win/tclWinPipe.c @@ -0,0 +1,3717 @@ +/* + * tclWinPipe.c -- + * + * This file implements the Windows-specific exec pipeline functions, the + * "pipe" channel driver, and the "pid" Tcl command. + * + * Copyright © 1996-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 + +/* + * The following variable is used to tell whether this module has been + * initialized. + */ + +static int initialized = 0; + +/* + * The pipeMutex locks around access to the initialized and procList + * variables, and it is used to protect background threads from being + * terminated while they are using APIs that hold locks. + */ + +TCL_DECLARE_MUTEX(pipeMutex) + +/* + * The following defines identify the various types of applications that run + * under windows. There is special case code for the various types. + */ + +#define APPL_NONE 0 +#define APPL_DOS 1 +#define APPL_WIN3X 2 +#define APPL_WIN32 3 + +/* + * The following constants and structures are used to encapsulate the state of + * various types of files used in a pipeline. This used to have a 1 && 2 that + * supported Win32s. + */ + +#define WIN_FILE 3 /* Basic Win32 file. */ + +/* + * This structure encapsulates the common state associated with all file types + * used in a pipeline. + */ + +typedef struct { + int type; /* One of the file types defined above. */ + HANDLE handle; /* Open file handle. */ +} WinFile; + +/* + * This list is used to map from pids to process handles. + */ + +typedef struct ProcInfo { + HANDLE hProcess; + int dwProcessId; + struct ProcInfo *nextPtr; +} ProcInfo; + +static ProcInfo *procList; + +/* + * Bit masks used in the flags field of the PipeInfo structure below. + */ + +#define PIPE_PENDING (1<<0) /* Message is pending in the queue. */ +#define PIPE_ASYNC (1<<1) /* Channel is non-blocking. */ + +/* + * Bit masks used in the sharedFlags field of the PipeInfo structure below. + */ + +#define PIPE_EOF (1<<2) /* Pipe has reached EOF. */ +#define PIPE_EXTRABYTE (1<<3) /* The reader thread has consumed one byte. */ + +/* + * TODO: It appears the whole EXTRABYTE machinery is in place to support + * outdated Win 95 systems. If this can be confirmed, much code can be + * deleted. + */ + +/* + * This structure describes per-instance data for a pipe based channel. + */ + +typedef struct PipeInfo { + struct PipeInfo *nextPtr; /* Pointer to next registered pipe. */ + Tcl_Channel channel; /* Pointer to channel structure. */ + int validMask; /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, or TCL_EXCEPTION: indicates + * which operations are valid on the file. */ + int watchMask; /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, or TCL_EXCEPTION: indicates + * which events should be reported. */ + int flags; /* State flags, see above for a list. */ + TclFile readFile; /* Output from pipe. */ + TclFile writeFile; /* Input from pipe. */ + TclFile errorFile; /* Error output from pipe. */ + size_t numPids; /* Number of processes attached to pipe. */ + Tcl_Pid *pidPtr; /* Pids of attached processes. */ + Tcl_ThreadId threadId; /* Thread to which events should be reported. + * This value is used by the reader/writer + * threads. */ + TclPipeThreadInfo *writeTI; /* Thread info of writer and reader, this */ + TclPipeThreadInfo *readTI; /* structure owned by corresponding thread. */ + HANDLE writeThread; /* Handle to writer thread. */ + HANDLE readThread; /* Handle to reader thread. */ + + HANDLE writable; /* Manual-reset event to signal when the + * writer thread has finished waiting for the + * current buffer to be written. */ + HANDLE readable; /* Manual-reset event to signal when the + * reader thread has finished waiting for + * input. */ + DWORD writeError; /* An error caused by the last background + * write. Set to 0 if no error has been + * detected. This word is shared with the + * writer thread so access must be + * synchronized with the writable object. */ + char *writeBuf; /* Current background output buffer. Access is + * synchronized with the writable object. */ + int writeBufLen; /* Size of write buffer. Access is + * synchronized with the writable object. */ + int toWrite; /* Current amount to be written. Access is + * synchronized with the writable object. */ + int readFlags; /* Flags that are shared with the reader + * thread. Access is synchronized with the + * readable object. */ + char extraByte; /* Buffer for extra character consumed by + * reader thread. This byte is shared with the + * reader thread so access must be + * synchronized with the readable object. */ +} PipeInfo; + +typedef struct { + /* + * The following pointer refers to the head of the list of pipes that are + * being watched for file events. + */ + + PipeInfo *firstPipePtr; +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +/* + * The following structure is what is added to the Tcl event queue when pipe + * events are generated. + */ + +typedef struct { + Tcl_Event header; /* Information that is standard for all + * events. */ + PipeInfo *infoPtr; /* Pointer to pipe info structure. Note that + * we still have to verify that the pipe + * exists before dereferencing this + * pointer. */ +} PipeEvent; + +/* + * Declarations for functions used only in this file. + */ + +static int ApplicationType(Tcl_Interp *interp, + const char *fileName, char *fullName); +static void BuildCommandLine(const char *executable, size_t argc, + const char **argv, Tcl_DString *linePtr); +static BOOL HasConsole(void); +static int PipeBlockModeProc(void *instanceData, int mode); +static void PipeCheckProc(void *clientData, int flags); +static int PipeClose2Proc(void *instanceData, + Tcl_Interp *interp, int flags); +static int PipeEventProc(Tcl_Event *evPtr, int flags); +static int PipeGetHandleProc(void *instanceData, + int direction, void **handlePtr); +static void PipeInit(void); +static int PipeInputProc(void *instanceData, char *buf, + int toRead, int *errorCode); +static int PipeOutputProc(void *instanceData, + const char *buf, int toWrite, int *errorCode); +static DWORD WINAPI PipeReaderThread(LPVOID arg); +static void PipeSetupProc(void *clientData, int flags); +static void PipeWatchProc(void *instanceData, int mask); +static DWORD WINAPI PipeWriterThread(LPVOID arg); +static int TempFileName(WCHAR name[MAX_PATH]); +static int WaitForRead(PipeInfo *infoPtr, int blocking); +static void PipeThreadActionProc(void *instanceData, + int action); + +/* + * This structure describes the channel type structure for command pipe based + * I/O. + */ + +static const Tcl_ChannelType pipeChannelType = { + "pipe", + TCL_CHANNEL_VERSION_5, + NULL, /* Deprecated. */ + PipeInputProc, + PipeOutputProc, + NULL, /* Deprecated. */ + NULL, /* Set option proc. */ + NULL, /* Get option proc. */ + PipeWatchProc, + PipeGetHandleProc, + PipeClose2Proc, + PipeBlockModeProc, + NULL, /* Flush proc. */ + NULL, /* Bubbled event handler proc. */ + NULL, /* Seek proc. */ + PipeThreadActionProc, + NULL /* Truncate proc. */ +}; + +/* + *---------------------------------------------------------------------- + * + * PipeInit -- + * + * This function initializes the static variables for this file. + * + * Results: + * None. + * + * Side effects: + * Creates a new event source. + * + *---------------------------------------------------------------------- + */ + +static void +PipeInit(void) +{ + ThreadSpecificData *tsdPtr; + + /* + * Check the initialized flag first, then check again in the mutex. This + * is a speed enhancement. + */ + + if (!initialized) { + Tcl_MutexLock(&pipeMutex); + if (!initialized) { + initialized = 1; + procList = NULL; + } + Tcl_MutexUnlock(&pipeMutex); + } + + tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); + if (tsdPtr == NULL) { + tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->firstPipePtr = NULL; + Tcl_CreateEventSource(PipeSetupProc, PipeCheckProc, NULL); + } +} + +/* + *---------------------------------------------------------------------- + * + * TclpFinalizePipes -- + * + * This function is called from Tcl_FinalizeThread to finalize the + * platform specific pipe subsystem. + * + * Results: + * None. + * + * Side effects: + * Removes the pipe event source. + * + *---------------------------------------------------------------------- + */ + +void +TclpFinalizePipes(void) +{ + ThreadSpecificData *tsdPtr; + + tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); + if (tsdPtr != NULL) { + Tcl_DeleteEventSource(PipeSetupProc, PipeCheckProc, NULL); + } +} + +/* + *---------------------------------------------------------------------- + * + * PipeSetupProc -- + * + * This function is invoked before Tcl_DoOneEvent blocks waiting for an + * event. + * + * Results: + * None. + * + * Side effects: + * Adjusts the block time if needed. + * + *---------------------------------------------------------------------- + */ + +void +PipeSetupProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + PipeInfo *infoPtr; + Tcl_Time blockTime = { 0, 0 }; + int block = 1; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + /* + * Look to see if any events are already pending. If they are, poll. + */ + + for (infoPtr = tsdPtr->firstPipePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->watchMask & TCL_WRITABLE) { + if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { + block = 0; + } + } + if (infoPtr->watchMask & TCL_READABLE) { + if (WaitForRead(infoPtr, 0) >= 0) { + block = 0; + } + } + } + if (!block) { + Tcl_SetMaxBlockTime(&blockTime); + } +} + +/* + *---------------------------------------------------------------------- + * + * PipeCheckProc -- + * + * This function is called by Tcl_DoOneEvent to check the pipe event + * source for events. + * + * Results: + * None. + * + * Side effects: + * May queue an event. + * + *---------------------------------------------------------------------- + */ + +static void +PipeCheckProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + PipeInfo *infoPtr; + PipeEvent *evPtr; + int needEvent; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + /* + * Queue events for any ready pipes that don't already have events queued. + */ + + for (infoPtr = tsdPtr->firstPipePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->flags & PIPE_PENDING) { + continue; + } + + /* + * Queue an event if the pipe is signaled for reading or writing. + */ + + needEvent = 0; + if ((infoPtr->watchMask & TCL_WRITABLE) && + (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT)) { + needEvent = 1; + } + + if ((infoPtr->watchMask & TCL_READABLE) && + (WaitForRead(infoPtr, 0) >= 0)) { + needEvent = 1; + } + + if (needEvent) { + infoPtr->flags |= PIPE_PENDING; + evPtr = (PipeEvent *)Tcl_Alloc(sizeof(PipeEvent)); + evPtr->header.proc = PipeEventProc; + evPtr->infoPtr = infoPtr; + Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TclWinMakeFile -- + * + * This function constructs a new TclFile from a given data and type + * value. + * + * Results: + * Returns a newly allocated WinFile as a TclFile. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +TclFile +TclWinMakeFile( + HANDLE handle) /* Type-specific data. */ +{ + WinFile *filePtr; + + filePtr = (WinFile *)Tcl_Alloc(sizeof(WinFile)); + filePtr->type = WIN_FILE; + filePtr->handle = handle; + + return (TclFile)filePtr; +} + +/* + *---------------------------------------------------------------------- + * + * TempFileName -- + * + * Gets a temporary file name and deals with the fact that the temporary + * file path provided by Windows may not actually exist if the TMP or + * TEMP environment variables refer to a non-existent directory. + * + * Results: + * 0 if error, non-zero otherwise. If non-zero is returned, the name + * buffer will be filled with a name that can be used to construct a + * temporary file. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TempFileName( + WCHAR name[MAX_PATH]) /* Buffer in which name for temporary file + * gets stored. */ +{ + const WCHAR *prefix = L"TCL"; + if (GetTempPathW(MAX_PATH, name) != 0) { + if (GetTempFileNameW(name, prefix, 0, name) != 0) { + return 1; + } + } + name[0] = '.'; + name[1] = '\0'; + return GetTempFileNameW(name, prefix, 0, name); +} + +/* + *---------------------------------------------------------------------- + * + * TclpMakeFile -- + * + * Make a TclFile from a channel. + * + * Results: + * Returns a new TclFile or NULL on failure. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +TclFile +TclpMakeFile( + Tcl_Channel channel, /* Channel to get file from. */ + int direction) /* Either TCL_READABLE or TCL_WRITABLE. */ +{ + HANDLE handle; + + if (Tcl_GetChannelHandle(channel, direction, + (void **) &handle) == TCL_OK) { + return TclWinMakeFile(handle); + } else { + return (TclFile) NULL; + } +} + +/* + *---------------------------------------------------------------------- + * + * TclpOpenFile -- + * + * This function opens files for use in a pipeline. + * + * Results: + * Returns a newly allocated TclFile structure containing the file + * handle. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +TclFile +TclpOpenFile( + const char *path, /* The name of the file to open. */ + int mode) /* In what mode to open the file? */ +{ + HANDLE handle; + DWORD accessMode, createMode, shareMode, flags; + Tcl_DString ds; + const WCHAR *nativePath; + + /* + * Map the access bits to the NT access mode. + */ + + switch (mode & O_ACCMODE) { + case O_RDONLY: + accessMode = GENERIC_READ; + break; + case O_WRONLY: + accessMode = GENERIC_WRITE; + break; + case O_RDWR: + accessMode = (GENERIC_READ | GENERIC_WRITE); + break; + default: + Tcl_WinConvertError(ERROR_INVALID_FUNCTION); + return NULL; + } + + /* + * Map the creation flags to the NT create mode. + */ + + switch (mode & (O_CREAT | O_EXCL | O_TRUNC)) { + case (O_CREAT | O_EXCL): + case (O_CREAT | O_EXCL | O_TRUNC): + createMode = CREATE_NEW; + break; + case (O_CREAT | O_TRUNC): + createMode = CREATE_ALWAYS; + break; + case O_CREAT: + createMode = OPEN_ALWAYS; + break; + case O_TRUNC: + case (O_TRUNC | O_EXCL): + createMode = TRUNCATE_EXISTING; + break; + default: + createMode = OPEN_EXISTING; + break; + } + + Tcl_DStringInit(&ds); + nativePath = Tcl_UtfToWCharDString(path, TCL_INDEX_NONE, &ds); + + /* + * If the file is not being created, use the existing file attributes. + */ + + flags = 0; + if (!(mode & O_CREAT)) { + flags = GetFileAttributesW(nativePath); + if (flags == 0xFFFFFFFF) { + flags = 0; + } + } + + /* + * Set up the file sharing mode. We want to allow simultaneous access. + */ + + shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + /* + * Now we get to create the file. + */ + + handle = CreateFileW(nativePath, accessMode, shareMode, + NULL, createMode, flags, NULL); + Tcl_DStringFree(&ds); + + if (handle == INVALID_HANDLE_VALUE) { + DWORD err; + + err = GetLastError(); + if ((err & 0xFFFFL) == ERROR_OPEN_FAILED) { + err = (mode & O_CREAT) ? ERROR_FILE_EXISTS : ERROR_FILE_NOT_FOUND; + } + Tcl_WinConvertError(err); + return NULL; + } + + /* + * Seek to the end of file if we are writing. + */ + + if (mode & (O_WRONLY|O_APPEND)) { + SetFilePointer(handle, 0, NULL, FILE_END); + } + + return TclWinMakeFile(handle); +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreateTempFile -- + * + * This function opens a unique file with the property that it will be + * deleted when its file handle is closed. The temporary file is created + * in the system temporary directory. + * + * Results: + * Returns a valid TclFile, or NULL on failure. + * + * Side effects: + * Creates a new temporary file. + * + *---------------------------------------------------------------------- + */ + +TclFile +TclpCreateTempFile( + const char *contents) /* String to write into temp file, or NULL. */ +{ + WCHAR name[MAX_PATH]; + const char *native = NULL; + Tcl_DString dstring; + HANDLE handle; + + if (TempFileName(name) == 0) { + return NULL; + } + + handle = CreateFileW(name, + GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, NULL); + if (handle == INVALID_HANDLE_VALUE) { + goto error; + } + + /* + * Write the file out, doing line translations on the way. + */ + + if (contents != NULL) { + DWORD result, length; + const char *p; + int toCopy; + + /* + * Convert the contents from UTF to native encoding + */ + + if (Tcl_UtfToExternalDStringEx(NULL, NULL, contents, TCL_INDEX_NONE, 0, &dstring, NULL) != TCL_OK) { + goto error; + } + native = Tcl_DStringValue(&dstring); + + toCopy = Tcl_DStringLength(&dstring); + for (p = native; toCopy > 0; p++, toCopy--) { + if (*p == '\n') { + length = p - native; + if (length > 0) { + if (!WriteFile(handle, native, length, &result, NULL)) { + goto error; + } + } + if (!WriteFile(handle, "\r\n", 2, &result, NULL)) { + goto error; + } + native = p+1; + } + } + length = p - native; + if (length > 0) { + if (!WriteFile(handle, native, length, &result, NULL)) { + goto error; + } + } + Tcl_DStringFree(&dstring); + if (SetFilePointer(handle, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF) { + goto error; + } + } + + return TclWinMakeFile(handle); + + error: + /* + * Free the native representation of the contents if necessary. + */ + + if (contents != NULL) { + Tcl_DStringFree(&dstring); + } + + if (native != NULL) { + Tcl_WinConvertError(GetLastError()); + } + CloseHandle(handle); + DeleteFileW(name); + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * TclpTempFileName -- + * + * This function returns a unique filename. + * + * Results: + * Returns a valid Tcl_Obj* with refCount 0, or NULL on failure. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpTempFileName(void) +{ + WCHAR fileName[MAX_PATH]; + + if (TempFileName(fileName) == 0) { + return NULL; + } + + return TclpNativeToNormalized(fileName); +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreatePipe -- + * + * Creates an anonymous pipe. + * + * Results: + * Returns 1 on success, 0 on failure. + * + * Side effects: + * Creates a pipe. + * + *---------------------------------------------------------------------- + */ + +int +TclpCreatePipe( + TclFile *readPipe, /* Location to store file handle for read side + * of pipe. */ + TclFile *writePipe) /* Location to store file handle for write + * side of pipe. */ +{ + HANDLE readHandle, writeHandle; + + if (CreatePipe(&readHandle, &writeHandle, NULL, 0) != 0) { + *readPipe = TclWinMakeFile(readHandle); + *writePipe = TclWinMakeFile(writeHandle); + return 1; + } + + Tcl_WinConvertError(GetLastError()); + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclpCloseFile -- + * + * Closes a pipeline file handle. These handles are created by + * TclpOpenFile, TclpCreatePipe, or TclpMakeFile. + * + * Results: + * 0 on success, -1 on failure. + * + * Side effects: + * The file is closed and deallocated. + * + *---------------------------------------------------------------------- + */ + +int +TclpCloseFile( + TclFile file) /* The file to close. */ +{ + WinFile *filePtr = (WinFile *) file; + + switch (filePtr->type) { + case WIN_FILE: + /* + * Don't close the Win32 handle if the handle is a standard channel + * during the thread exit process. Otherwise, one thread may kill the + * stdio of another. + */ + + if (!TclInThreadExit() + || ((GetStdHandle(STD_INPUT_HANDLE) != filePtr->handle) + && (GetStdHandle(STD_OUTPUT_HANDLE) != filePtr->handle) + && (GetStdHandle(STD_ERROR_HANDLE) != filePtr->handle))) { + if (filePtr->handle != NULL && + CloseHandle(filePtr->handle) == FALSE) { + Tcl_WinConvertError(GetLastError()); + Tcl_Free(filePtr); + return -1; + } + } + break; + + default: + Tcl_Panic("TclpCloseFile: unexpected file type"); + } + + Tcl_Free(filePtr); + return 0; +} + +/* + *-------------------------------------------------------------------------- + * + * TclpGetPid -- + * + * Given a HANDLE to a child process, return the process id for that + * child process. + * + * Results: + * Returns the process id for the child process. If the pid was not known + * by Tcl, either because the pid was not created by Tcl or the child + * process has already been reaped, TCL_INDEX_NONE is returned. + * + * Side effects: + * None. + * + *-------------------------------------------------------------------------- + */ + +Tcl_Size +TclpGetPid( + Tcl_Pid pid) /* The HANDLE of the child process. */ +{ + ProcInfo *infoPtr; + + PipeInit(); + + Tcl_MutexLock(&pipeMutex); + for (infoPtr = procList; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { + if (infoPtr->dwProcessId == (Tcl_Size)pid) { + Tcl_MutexUnlock(&pipeMutex); + return infoPtr->dwProcessId; + } + } + Tcl_MutexUnlock(&pipeMutex); + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreateProcess -- + * + * Create a child process that has the specified files as its standard + * input, output, and error. The child process runs asynchronously under + * Windows NT and Windows 9x, and runs with the same environment + * variables as the creating process. + * + * The complete Windows search path is searched to find the specified + * executable. If an executable by the given name is not found, + * automatically tries appending standard extensions to the + * executable name. + * + * Results: + * The return value is TCL_ERROR and an error message is left in the + * interp's result if there was a problem creating the child process. + * Otherwise, the return value is TCL_OK and *pidPtr is filled with the + * process id of the child process. + * + * Side effects: + * A process is created. + * + *---------------------------------------------------------------------- + */ + +int +TclpCreateProcess( + Tcl_Interp *interp, /* Interpreter in which to leave errors that + * occurred when creating the child process. + * Error messages from the child process + * itself are sent to errorFile. */ + size_t argc, /* Number of arguments in following array. */ + const char **argv, /* Array of argument strings. argv[0] contains + * the name of the executable converted to + * native format (using the + * Tcl_TranslateFileName call). Additional + * arguments have not been converted. */ + TclFile inputFile, /* If non-NULL, gives the file to use as input + * for the child process. If inputFile file is + * not readable or is NULL, the child will + * receive no standard input. */ + TclFile outputFile, /* If non-NULL, gives the file that receives + * output from the child process. If + * outputFile file is not writable or is + * NULL, output from the child will be + * discarded. */ + TclFile errorFile, /* If non-NULL, gives the file that receives + * errors from the child process. If errorFile + * file is not writable or is NULL, errors + * from the child will be discarded. errorFile + * may be the same as outputFile. */ + Tcl_Pid *pidPtr) /* If this function is successful, pidPtr is + * filled with the process id of the child + * process. */ +{ + int result, applType, createFlags; + Tcl_DString cmdLine; /* Complete command line (WCHAR). */ + STARTUPINFOW startInfo; + PROCESS_INFORMATION procInfo; + SECURITY_ATTRIBUTES secAtts; + HANDLE hProcess, h, inputHandle, outputHandle, errorHandle; + char execPath[MAX_PATH * 3]; + WinFile *filePtr; + + PipeInit(); + + applType = ApplicationType(interp, argv[0], execPath); + if (applType == APPL_NONE) { + return TCL_ERROR; + } + + result = TCL_ERROR; + Tcl_DStringInit(&cmdLine); + hProcess = GetCurrentProcess(); + + /* + * STARTF_USESTDHANDLES must be used to pass handles to child process. + * Using SetStdHandle() and/or dup2() only works when a console mode + * parent process is spawning an attached console mode child process. + */ + + ZeroMemory(&startInfo, sizeof(startInfo)); + startInfo.cb = sizeof(startInfo); + startInfo.dwFlags = STARTF_USESTDHANDLES; + startInfo.hStdInput = INVALID_HANDLE_VALUE; + startInfo.hStdOutput= INVALID_HANDLE_VALUE; + startInfo.hStdError = INVALID_HANDLE_VALUE; + + secAtts.nLength = sizeof(SECURITY_ATTRIBUTES); + secAtts.lpSecurityDescriptor = NULL; + secAtts.bInheritHandle = TRUE; + + /* + * We have to check the type of each file, since we cannot duplicate some + * file types. + */ + + inputHandle = INVALID_HANDLE_VALUE; + if (inputFile != NULL) { + filePtr = (WinFile *)inputFile; + if (filePtr->type == WIN_FILE) { + inputHandle = filePtr->handle; + } + } + outputHandle = INVALID_HANDLE_VALUE; + if (outputFile != NULL) { + filePtr = (WinFile *)outputFile; + if (filePtr->type == WIN_FILE) { + outputHandle = filePtr->handle; + } + } + errorHandle = INVALID_HANDLE_VALUE; + if (errorFile != NULL) { + filePtr = (WinFile *)errorFile; + if (filePtr->type == WIN_FILE) { + errorHandle = filePtr->handle; + } + } + + /* + * Duplicate all the handles which will be passed off as stdin, stdout and + * stderr of the child process. The duplicate handles are set to be + * inheritable, so the child process can use them. + */ + + if (inputHandle == INVALID_HANDLE_VALUE) { + /* + * If handle was not set, stdin should return immediate EOF. Under + * Windows95, some applications (both 16 and 32 bit!) cannot read from + * the NUL device; they read from console instead. When running tk, + * this is fatal because the child process would hang forever waiting + * for EOF from the unmapped console window used by the helper + * application. + * + * Fortunately, the helper application detects a closed pipe as an + * immediate EOF and can pass that information to the child process. + */ + + if (CreatePipe(&startInfo.hStdInput, &h, &secAtts, 0) != FALSE) { + CloseHandle(h); + } + } else { + DuplicateHandle(hProcess, inputHandle, hProcess, &startInfo.hStdInput, + 0, TRUE, DUPLICATE_SAME_ACCESS); + } + if (startInfo.hStdInput == INVALID_HANDLE_VALUE) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't duplicate input handle: %s", + Tcl_PosixError(interp))); + goto end; + } + + if (outputHandle == INVALID_HANDLE_VALUE) { + /* + * If handle was not set, output should be sent to an infinitely deep + * sink. Under Windows 95, some 16 bit applications cannot have stdout + * redirected to NUL; they send their output to the console instead. + * Some applications, like "more" or "dir /p", when outputting + * multiple pages to the console, also then try and read from the + * console to go the next page. When running tk, this is fatal because + * the child process would hang forever waiting for input from the + * unmapped console window used by the helper application. + * + * Fortunately, the helper application will detect a closed pipe as a + * sink. + */ + + startInfo.hStdOutput = CreateFileW(L"NUL:", GENERIC_WRITE, 0, + &secAtts, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } else { + DuplicateHandle(hProcess, outputHandle, hProcess, + &startInfo.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS); + } + if (startInfo.hStdOutput == INVALID_HANDLE_VALUE) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't duplicate output handle: %s", + Tcl_PosixError(interp))); + goto end; + } + + if (errorHandle == INVALID_HANDLE_VALUE) { + /* + * If handle was not set, errors should be sent to an infinitely deep + * sink. + */ + + startInfo.hStdError = CreateFileW(L"NUL:", GENERIC_WRITE, 0, + &secAtts, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + } else { + DuplicateHandle(hProcess, errorHandle, hProcess, &startInfo.hStdError, + 0, TRUE, DUPLICATE_SAME_ACCESS); + } + if (startInfo.hStdError == INVALID_HANDLE_VALUE) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't duplicate error handle: %s", + Tcl_PosixError(interp))); + goto end; + } + + /* + * If we do not have a console window, then we must run DOS and WIN32 + * console mode applications as detached processes. This tells the loader + * that the child application should not inherit the console, and that it + * should not create a new console window for the child application. The + * child application should get its stdio from the redirection handles + * provided by this application, and run in the background. + * + * If we are starting a GUI process, they don't automatically get a + * console, so it doesn't matter if they are started as foreground or + * detached processes. The GUI window will still pop up to the foreground. + */ + + if (HasConsole()) { + createFlags = 0; + } else if (applType == APPL_DOS) { + /* + * Under NT, 16-bit DOS applications will not run unless they can + * be attached to a console. If we are running without a console, + * run the 16-bit program as an normal process inside of a hidden + * console application, and then run that hidden console as a + * detached process. + */ + + startInfo.wShowWindow = SW_HIDE; + startInfo.dwFlags |= STARTF_USESHOWWINDOW; + createFlags = CREATE_NEW_CONSOLE; + TclDStringAppendLiteral(&cmdLine, "cmd.exe /c"); + } else { + createFlags = DETACHED_PROCESS; + } + + /* + * cmdLine gets the full command line used to invoke the executable, + * including the name of the executable itself. The command line arguments + * in argv[] are stored in cmdLine separated by spaces. Special characters + * in individual arguments from argv[] must be quoted when being stored in + * cmdLine. + * + * When calling any application, bear in mind that arguments that specify + * a path name are not converted. If an argument contains forward slashes + * as path separators, it may or may not be recognized as a path name, + * depending on the program. In general, most applications accept forward + * slashes only as option delimiters and backslashes only as paths. + * + * Additionally, when calling a 16-bit dos or windows application, all + * path names must use the short, cryptic, path format (e.g., using + * ab~1.def instead of "a b.default"). + */ + + BuildCommandLine(execPath, argc, argv, &cmdLine); + + if (CreateProcessW(NULL, (WCHAR *) Tcl_DStringValue(&cmdLine), + NULL, NULL, TRUE, (DWORD) createFlags, NULL, NULL, &startInfo, + &procInfo) == 0) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("couldn't execute \"%s\": %s", + argv[0], Tcl_PosixError(interp))); + goto end; + } + + /* + * This wait is used to force the OS to give some time to the DOS process. + */ + + if (applType == APPL_DOS) { + WaitForSingleObject(procInfo.hProcess, 50); + } + + /* + * "When an application spawns a process repeatedly, a new thread instance + * will be created for each process but the previous instances may not be + * cleaned up. This results in a significant virtual memory loss each time + * the process is spawned. If there is a WaitForInputIdle() call between + * CreateProcessW() and CloseHandle(), the problem does not occur." PSS ID + * Number: Q124121 + */ + + WaitForInputIdle(procInfo.hProcess, 5000); + CloseHandle(procInfo.hThread); + + *pidPtr = (Tcl_Pid)INT2PTR(procInfo.dwProcessId); + if (*pidPtr != 0) { + TclWinAddProcess(procInfo.hProcess, procInfo.dwProcessId); + } + result = TCL_OK; + + end: + Tcl_DStringFree(&cmdLine); + if (startInfo.hStdInput != INVALID_HANDLE_VALUE) { + CloseHandle(startInfo.hStdInput); + } + if (startInfo.hStdOutput != INVALID_HANDLE_VALUE) { + CloseHandle(startInfo.hStdOutput); + } + if (startInfo.hStdError != INVALID_HANDLE_VALUE) { + CloseHandle(startInfo.hStdError); + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * HasConsole -- + * + * Determines whether the current application is attached to a console. + * + * Results: + * Returns TRUE if this application has a console, else FALSE. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static BOOL +HasConsole(void) +{ + HANDLE handle; + + handle = CreateFileW(L"CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle); + return TRUE; + } else { + return FALSE; + } +} + +/* + *-------------------------------------------------------------------- + * + * ApplicationType -- + * + * Search for the specified program and identify if it refers to a DOS, + * Windows 3.X, or Win32 program. Used to determine how to invoke a + * program, or if it can even be invoked. + * + * It is possible to almost positively identify DOS and Windows + * applications that contain the appropriate magic numbers. However, DOS + * .com files do not seem to contain a magic number; if the program name + * ends with .com and could not be identified as a Windows .com file, it + * will be assumed to be a DOS application, even if it was just random + * data. If the program name does not end with .com, no such assumption + * is made. + * + * The Win32 function GetBinaryType incorrectly identifies any junk file + * that ends with .exe as a dos executable and some executables that + * don't end with .exe as not executable. Plus it doesn't exist under + * win95, so I won't feel bad about reimplementing functionality. + * + * Results: + * The return value is one of APPL_DOS, APPL_WIN3X, or APPL_WIN32 if the + * filename referred to the corresponding application type. If the file + * name could not be found or did not refer to any known application + * type, APPL_NONE is returned and an error message is left in interp. + * .bat files are identified as APPL_DOS. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +ApplicationType( + Tcl_Interp *interp, /* Interp, for error message. */ + const char *originalName, /* Name of the application to find. */ + char fullName[]) /* Filled with complete path to + * application. */ +{ + int applType, i, nameLen, found; + HANDLE hFile; + WCHAR *rest; + char *ext; + char buf[2]; + DWORD attr, read; + IMAGE_DOS_HEADER header; + Tcl_DString nameBuf, ds; + const WCHAR *nativeName; + WCHAR nativeFullPath[MAX_PATH]; + static const char extensions[][5] = {"", ".com", ".exe", ".bat", ".cmd"}; + + /* + * Look for the program as an external program. First try the name as it + * is, then try adding .com, .exe, .bat and .cmd, in that order, to the name, + * looking for an executable. + * + * Using the raw SearchPathW() function doesn't do quite what is necessary. + * If the name of the executable already contains a '.' character, it will + * not try appending the specified extension when searching (in other + * words, SearchPath will not find the program "a.b.exe" if the arguments + * specified "a.b" and ".exe"). So, first look for the file as it is + * named. Then manually append the extensions, looking for a match. + */ + + applType = APPL_NONE; + Tcl_DStringInit(&nameBuf); + Tcl_DStringAppend(&nameBuf, originalName, TCL_INDEX_NONE); + nameLen = Tcl_DStringLength(&nameBuf); + + for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) { + Tcl_DStringSetLength(&nameBuf, nameLen); + Tcl_DStringAppend(&nameBuf, extensions[i], TCL_INDEX_NONE); + Tcl_DStringInit(&ds); + nativeName = Tcl_UtfToWCharDString(Tcl_DStringValue(&nameBuf), + Tcl_DStringLength(&nameBuf), &ds); + found = SearchPathW(NULL, nativeName, NULL, MAX_PATH, + nativeFullPath, &rest); + Tcl_DStringFree(&ds); + if (found == 0) { + continue; + } + + /* + * Ignore matches on directories or data files, return if identified a + * known type. + */ + + attr = GetFileAttributesW(nativeFullPath); + if ((attr == 0xFFFFFFFF) || (attr & FILE_ATTRIBUTE_DIRECTORY)) { + continue; + } + Tcl_DStringInit(&ds); + strcpy(fullName, Tcl_WCharToUtfDString(nativeFullPath, TCL_INDEX_NONE, &ds)); + Tcl_DStringFree(&ds); + + ext = strrchr(fullName, '.'); + if ((ext != NULL) && + (strcasecmp(ext, ".cmd") == 0 || strcasecmp(ext, ".bat") == 0)) { + applType = APPL_DOS; + break; + } + + hFile = CreateFileW(nativeFullPath, + GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + continue; + } + + if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { + /* + * But [4f0b5767ac]. Likely a App Execution Alias. This can only + * be a Win32 APP. Attempt to ReadFile below will fail. We assume + * that if it is on the PATH, and it is a reparse point, it is an + * App Execution Alias. + */ + CloseHandle(hFile); + applType = APPL_WIN32; + break; + } + + header.e_magic = 0; + ReadFile(hFile, (void *)&header, sizeof(header), &read, NULL); + if (header.e_magic != IMAGE_DOS_SIGNATURE) { + /* + * Doesn't have the magic number for relocatable executables. If + * filename ends with .com, assume it's a DOS application anyhow. + * Note that we didn't make this assumption at first, because some + * supposed .com files are really 32-bit executables with all the + * magic numbers and everything. + */ + + CloseHandle(hFile); + if ((ext != NULL) && (strcasecmp(ext, ".com") == 0)) { + applType = APPL_DOS; + break; + } + continue; + } + if (header.e_lfarlc != sizeof(header)) { + /* + * All Windows 3.X and Win32 and some DOS programs have this value + * set here. If it doesn't, assume that since it already had the + * other magic number it was a DOS application. + */ + + CloseHandle(hFile); + applType = APPL_DOS; + break; + } + + /* + * The DWORD at header.e_lfanew points to yet another magic number. + */ + + buf[0] = '\0'; + SetFilePointer(hFile, header.e_lfanew, NULL, FILE_BEGIN); + ReadFile(hFile, (void *)buf, 2, &read, NULL); + CloseHandle(hFile); + + if ((buf[0] == 'N') && (buf[1] == 'E')) { + applType = APPL_WIN3X; + } else if ((buf[0] == 'P') && (buf[1] == 'E')) { + applType = APPL_WIN32; + } else { + /* + * Strictly speaking, there should be a test that there is an 'L' + * and 'E' at buf[0..1], to identify the type as DOS, but of + * course we ran into a DOS executable that _doesn't_ have the + * magic number - specifically, one compiled using the Lahey + * Fortran90 compiler. + */ + + applType = APPL_DOS; + } + break; + } + Tcl_DStringFree(&nameBuf); + + if (applType == APPL_NONE) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("couldn't execute \"%s\": %s", + originalName, Tcl_PosixError(interp))); + return APPL_NONE; + } + + if (applType == APPL_WIN3X) { + /* + * Replace long path name of executable with short path name for + * 16-bit applications. Otherwise the application may not be able to + * correctly parse its own command line to separate off the + * application name from the arguments. + */ + + GetShortPathNameW(nativeFullPath, nativeFullPath, MAX_PATH); + Tcl_DStringInit(&ds); + strcpy(fullName, Tcl_WCharToUtfDString(nativeFullPath, TCL_INDEX_NONE, &ds)); + Tcl_DStringFree(&ds); + } + return applType; +} + +/* + *---------------------------------------------------------------------- + * + * BuildCommandLine -- + * + * The command line arguments are stored in linePtr separated by spaces, + * in a form that CreateProcessW() understands. Special characters in + * individual arguments from argv[] must be quoted when being stored in + * cmdLine. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static const char * +BuildCmdLineBypassBS( + const char *current, + const char **bspos) +{ + /* + * Mark first backslash position. + */ + + if (!*bspos) { + *bspos = current; + } + do { + current++; + } while (*current == '\\'); + return current; +} + +static void +QuoteCmdLineBackslash( + Tcl_DString *dsPtr, + const char *start, + const char *current, + const char *bspos) +{ + if (!bspos) { + if (current > start) { /* part before current (special) */ + Tcl_DStringAppend(dsPtr, start, (int) (current - start)); + } + } else { + if (bspos > start) { /* part before first backslash */ + Tcl_DStringAppend(dsPtr, start, (int) (bspos - start)); + } + while (bspos++ < current) { /* each backslash twice */ + TclDStringAppendLiteral(dsPtr, "\\\\"); + } + } +} + +static const char * +QuoteCmdLinePart( + Tcl_DString *dsPtr, + const char *start, + const char *special, + const char *specMetaChars, + const char **bspos) +{ + if (!*bspos) { + /* + * Rest before special (before quote). + */ + + QuoteCmdLineBackslash(dsPtr, start, special, NULL); + start = special; + } else { + /* + * Rest before first backslash and backslashes into new quoted block. + */ + + QuoteCmdLineBackslash(dsPtr, start, *bspos, NULL); + start = *bspos; + } + + /* + * escape all special chars enclosed in quotes like `"..."`, note that + * here we don't must escape `\` (with `\`), because it's outside of the + * main quotes, so `\` remains `\`, but important - not at end of part, + * because results as before the quote, so `%\%\` should be escaped as + * `"%\%"\\`). + */ + + TclDStringAppendLiteral(dsPtr, "\""); /* opening escape quote-char */ + do { + *bspos = NULL; + special++; + if (*special == '\\') { + /* + * Bypass backslashes (and mark first backslash position). + */ + + special = BuildCmdLineBypassBS(special, bspos); + if (*special == '\0') { + break; + } + } + } while (*special && strchr(specMetaChars, *special)); + if (!*bspos) { + /* + * Unescaped rest before quote. + */ + + QuoteCmdLineBackslash(dsPtr, start, special, NULL); + } else { + /* + * Unescaped rest before first backslash (rather belongs to the main + * block). + */ + + QuoteCmdLineBackslash(dsPtr, start, *bspos, NULL); + } + TclDStringAppendLiteral(dsPtr, "\""); /* closing escape quote-char */ + return special; +} + +static void +BuildCommandLine( + const char *executable, /* Full path of executable (including + * extension). Replacement for argv[0]. */ + size_t argc, /* Number of arguments. */ + const char **argv, /* Argument strings in UTF. */ + Tcl_DString *linePtr) /* Initialized Tcl_DString that receives the + * command line (WCHAR). */ +{ + const char *arg, *start, *special, *bspos; + int quote = 0; + size_t i; + Tcl_DString ds; +#ifdef TCL_WIN_PIPE_FULLESC + /* full escape inclusive %-subst avoidance */ + static const char specMetaChars[] = "&|^<>!()%"; + /* Characters to enclose in quotes if unpaired + * quote flag set. */ + static const char specMetaChars2[] = "%"; + /* Character to enclose in quotes in any case + * (regardless of unpaired-flag). */ +#else + /* escape considering quotation only (no %-subst avoidance) */ + static const char specMetaChars[] = "&|^<>!()"; + /* Characters to enclose in quotes if unpaired + * quote flag set. */ +#endif + /* + * Quote flags: + * CL_ESCAPE - escape argument; + * CL_QUOTE - enclose in quotes; + * CL_UNPAIRED - previous arguments chain contains unpaired quote-char; + */ + enum {CL_ESCAPE = 1, CL_QUOTE = 2, CL_UNPAIRED = 4}; + + Tcl_DStringInit(&ds); + + /* + * Prime the path. Add a space separator if we were primed with something. + */ + + TclDStringAppendDString(&ds, linePtr); + if (Tcl_DStringLength(linePtr) > 0) { + TclDStringAppendLiteral(&ds, " "); + } + + for (i = 0; i < argc; i++) { + if (i == 0) { + arg = executable; + } else { + arg = argv[i]; + TclDStringAppendLiteral(&ds, " "); + } + + quote &= ~(CL_ESCAPE|CL_QUOTE); /* reset escape flags */ + bspos = NULL; + if (arg[0] == '\0') { + quote = CL_QUOTE; + } else { + for (start = arg; + *start != '\0' && + (quote & (CL_ESCAPE|CL_QUOTE)) != (CL_ESCAPE|CL_QUOTE); + start++) { + if (*start & 0x80) { + continue; + } + if (TclIsSpaceProc(*start)) { + quote |= CL_QUOTE; /* quote only */ + if (bspos) { /* if backslash found, escape & quote */ + quote |= CL_ESCAPE; + break; + } + continue; + } + if (strchr(specMetaChars, *start)) { + quote |= (CL_ESCAPE|CL_QUOTE); /* escape & quote */ + break; + } + if (*start == '"') { + quote |= CL_ESCAPE; /* escape only */ + continue; + } + if (*start == '\\') { + bspos = start; + if (quote & CL_QUOTE) { /* if quote, escape & quote */ + quote |= CL_ESCAPE; + break; + } + continue; + } + } + bspos = NULL; + } + if (quote & CL_QUOTE) { + /* + * Start of argument (main opening quote-char). + */ + + TclDStringAppendLiteral(&ds, "\""); + } + if (!(quote & CL_ESCAPE)) { + /* + * Nothing to escape. + */ + + Tcl_DStringAppend(&ds, arg, TCL_INDEX_NONE); + } else { + start = arg; + for (special = arg; *special != '\0'; ) { + /* + * Position of `\` is important before quote or at end (equal + * `\"` because quoted). + */ + + if (*special == '\\') { + /* + * Bypass backslashes (and mark first backslash position) + */ + + special = BuildCmdLineBypassBS(special, &bspos); + if (*special == '\0') { + break; + } + } + /* ["] */ + if (*special == '"') { + /* + * Invert the unpaired flag - observe unpaired quotes + */ + + quote ^= CL_UNPAIRED; + + /* + * Add part before (and escape backslashes before quote). + */ + + QuoteCmdLineBackslash(&ds, start, special, bspos); + bspos = NULL; + + /* + * Escape using backslash + */ + + TclDStringAppendLiteral(&ds, "\\\""); + start = ++special; + continue; + } + + /* + * Unpaired (escaped) quote causes special handling on + * meta-chars + */ + + if ((quote & CL_UNPAIRED) && strchr(specMetaChars, *special)) { + special = QuoteCmdLinePart(&ds, start, special, + specMetaChars, &bspos); + + /* + * Start to current or first backslash + */ + + start = !bspos ? special : bspos; + continue; + } +#ifdef TCL_WIN_PIPE_FULLESC + /* + * Special case for % - should be enclosed always (paired + * also) + */ + + if (strchr(specMetaChars2, *special)) { + special = QuoteCmdLinePart(&ds, start, special, + specMetaChars2, &bspos); + + /* + * Start to current or first backslash. + */ + + start = !bspos ? special : bspos; + continue; + } +#endif + + /* + * Other not special (and not meta) character + */ + + bspos = NULL; /* reset last backslash position (not + * interesting) */ + special++; + } + + /* + * Rest of argument (and escape backslashes before closing main + * quote) + */ + + QuoteCmdLineBackslash(&ds, start, special, + (quote & CL_QUOTE) ? bspos : NULL); + } + if (quote & CL_QUOTE) { + /* + * End of argument (main closing quote-char) + */ + + TclDStringAppendLiteral(&ds, "\""); + } + } + Tcl_DStringFree(linePtr); + Tcl_DStringInit(linePtr); + Tcl_UtfToWCharDString(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds), linePtr); + Tcl_DStringFree(&ds); +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreateCommandChannel -- + * + * This function is called by Tcl_OpenCommandChannel to perform the + * platform specific channel initialization for a command channel. + * + * Results: + * Returns a new channel or NULL on failure. + * + * Side effects: + * Allocates a new channel. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +TclpCreateCommandChannel( + TclFile readFile, /* If non-null, gives the file for reading. */ + TclFile writeFile, /* If non-null, gives the file for writing. */ + TclFile errorFile, /* If non-null, gives the file where errors + * can be read. */ + size_t numPids, /* The number of pids in the pid array. */ + Tcl_Pid *pidPtr) /* An array of process identifiers. */ +{ + char channelName[16 + TCL_INTEGER_SPACE]; + PipeInfo *infoPtr = (PipeInfo *)Tcl_Alloc(sizeof(PipeInfo)); + + PipeInit(); + + infoPtr->watchMask = 0; + infoPtr->flags = 0; + infoPtr->readFlags = 0; + infoPtr->readFile = readFile; + infoPtr->writeFile = writeFile; + infoPtr->errorFile = errorFile; + infoPtr->numPids = numPids; + infoPtr->pidPtr = pidPtr; + infoPtr->writeBuf = 0; + infoPtr->writeBufLen = 0; + infoPtr->writeError = 0; + infoPtr->channel = NULL; + + infoPtr->validMask = 0; + + infoPtr->threadId = Tcl_GetCurrentThread(); + + if (readFile != NULL) { + /* + * Start the background reader thread. + */ + + infoPtr->readable = CreateEventW(NULL, TRUE, TRUE, NULL); + infoPtr->readThread = CreateThread(NULL, 256, PipeReaderThread, + TclPipeThreadCreateTI(&infoPtr->readTI, infoPtr), 0, NULL); + SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); + infoPtr->validMask |= TCL_READABLE; + } else { + infoPtr->readTI = NULL; + infoPtr->readThread = 0; + } + if (writeFile != NULL) { + /* + * Start the background writer thread. + */ + + infoPtr->writable = CreateEventW(NULL, TRUE, TRUE, NULL); + infoPtr->writeThread = CreateThread(NULL, 256, PipeWriterThread, + TclPipeThreadCreateTI(&infoPtr->writeTI, infoPtr), 0, NULL); + SetThreadPriority(infoPtr->writeThread, THREAD_PRIORITY_HIGHEST); + infoPtr->validMask |= TCL_WRITABLE; + } else { + infoPtr->writeTI = NULL; + infoPtr->writeThread = 0; + } + + /* + * For backward compatibility with previous versions of Tcl, we use + * "file%d" as the base name for pipes even though it would be more + * natural to use "pipe%d". Use the pointer to keep the channel names + * unique, in case channels share handles (stdin/stdout). + */ + + TclWinGenerateChannelName(channelName, "file", infoPtr); + infoPtr->channel = Tcl_CreateChannel(&pipeChannelType, channelName, + infoPtr, infoPtr->validMask); + + Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); + return infoPtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_CreatePipe -- + * + * System dependent interface to create a pipe for the [chan pipe] + * command. Stolen from TclX. + * + * Results: + * TCL_OK or TCL_ERROR. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_CreatePipe( + Tcl_Interp *interp, /* Errors returned in result.*/ + Tcl_Channel *rchan, /* Where to return the read side. */ + Tcl_Channel *wchan, /* Where to return the write side. */ + TCL_UNUSED(int) /*flags*/) /* Reserved for future use. */ +{ + HANDLE readHandle, writeHandle; + SECURITY_ATTRIBUTES sec; + + sec.nLength = sizeof(SECURITY_ATTRIBUTES); + sec.lpSecurityDescriptor = NULL; + sec.bInheritHandle = FALSE; + + if (!CreatePipe(&readHandle, &writeHandle, &sec, 0)) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "pipe creation failed: %s", Tcl_PosixError(interp))); + return TCL_ERROR; + } + + *rchan = Tcl_MakeFileChannel((void *)readHandle, TCL_READABLE); + Tcl_RegisterChannel(interp, *rchan); + + *wchan = Tcl_MakeFileChannel((void *)writeHandle, TCL_WRITABLE); + Tcl_RegisterChannel(interp, *wchan); + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclGetAndDetachPids -- + * + * Stores a list of the command PIDs for a command channel in the + * interp's result. + * + * Results: + * None. + * + * Side effects: + * Modifies the interp's result. + * + *---------------------------------------------------------------------- + */ + +void +TclGetAndDetachPids( + Tcl_Interp *interp, + Tcl_Channel chan) +{ + PipeInfo *pipePtr; + const Tcl_ChannelType *chanTypePtr; + Tcl_Obj *pidsObj; + size_t i; + + /* + * Punt if the channel is not a command channel. + */ + + chanTypePtr = Tcl_GetChannelType(chan); + if (chanTypePtr != &pipeChannelType) { + return; + } + + pipePtr = (PipeInfo *)Tcl_GetChannelInstanceData(chan); + TclNewObj(pidsObj); + for (i = 0; i < pipePtr->numPids; i++) { + Tcl_ListObjAppendElement(NULL, pidsObj, + Tcl_NewWideIntObj( + TclpGetPid(pipePtr->pidPtr[i]))); + Tcl_DetachPids(1, &pipePtr->pidPtr[i]); + } + Tcl_SetObjResult(interp, pidsObj); + if (pipePtr->numPids > 0) { + Tcl_Free(pipePtr->pidPtr); + pipePtr->numPids = 0; + } +} + +/* + *---------------------------------------------------------------------- + * + * PipeBlockModeProc -- + * + * Set blocking or non-blocking mode on channel. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or non-blocking mode. + * + *---------------------------------------------------------------------- + */ + +static int +PipeBlockModeProc( + void *instanceData, /* Instance data for channel. */ + int mode) /* TCL_MODE_BLOCKING or + * TCL_MODE_NONBLOCKING. */ +{ + PipeInfo *infoPtr = (PipeInfo *) instanceData; + + /* + * Pipes on Windows can not be switched between blocking and nonblocking, + * hence we have to emulate the behavior. This is done in the input + * function by checking against a bit in the state. We set or unset the + * bit here to cause the input function to emulate the correct behavior. + */ + + if (mode == TCL_MODE_NONBLOCKING) { + infoPtr->flags |= PIPE_ASYNC; + } else { + infoPtr->flags &= ~(PIPE_ASYNC); + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * PipeClose2Proc -- + * + * Closes a pipe based IO channel. + * + * Results: + * 0 on success, errno otherwise. + * + * Side effects: + * Closes the physical channel. + * + *---------------------------------------------------------------------- + */ + +static int +PipeClose2Proc( + void *instanceData, /* Pointer to PipeInfo structure. */ + Tcl_Interp *interp, /* For error reporting. */ + int flags) /* Flags that indicate which side to close. */ +{ + PipeInfo *pipePtr = (PipeInfo *) instanceData; + Tcl_Channel errChan; + int errorCode, result; + PipeInfo *infoPtr, **nextPtrPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int inExit = (TclInExit() || TclInThreadExit()); + + errorCode = 0; + result = 0; + + if ((!flags || flags & TCL_CLOSE_READ) && (pipePtr->readFile != NULL)) { + /* + * Clean up the background thread if necessary. Note that this must be + * done before we can close the file, since the thread may be blocking + * trying to read from the pipe. + */ + + if (pipePtr->readThread) { + + TclPipeThreadStop(&pipePtr->readTI, pipePtr->readThread); + CloseHandle(pipePtr->readThread); + CloseHandle(pipePtr->readable); + pipePtr->readThread = NULL; + } + if (TclpCloseFile(pipePtr->readFile) != 0) { + errorCode = errno; + } + pipePtr->validMask &= ~TCL_READABLE; + pipePtr->readFile = NULL; + } + if ((!flags || flags & TCL_CLOSE_WRITE) && (pipePtr->writeFile != NULL)) { + if (pipePtr->writeThread) { + + /* + * Wait for the writer thread to finish the current buffer, then + * terminate the thread and close the handles. If the channel is + * nonblocking or may block during exit, bail out since the worker + * thread is not interruptible and we want TIP#398-fast-exit. + */ + if ((pipePtr->flags & PIPE_ASYNC) && inExit) { + + /* give it a chance to leave honorably */ + TclPipeThreadStopSignal(&pipePtr->writeTI); + + if (WaitForSingleObject(pipePtr->writable, 20) == WAIT_TIMEOUT) { + return EWOULDBLOCK; + } + + } else { + + WaitForSingleObject(pipePtr->writable, inExit ? 5000 : INFINITE); + + } + + TclPipeThreadStop(&pipePtr->writeTI, pipePtr->writeThread); + + CloseHandle(pipePtr->writable); + CloseHandle(pipePtr->writeThread); + pipePtr->writeThread = NULL; + } + if (TclpCloseFile(pipePtr->writeFile) != 0) { + if (errorCode == 0) { + errorCode = errno; + } + } + pipePtr->validMask &= ~TCL_WRITABLE; + pipePtr->writeFile = NULL; + } + + pipePtr->watchMask &= pipePtr->validMask; + + /* + * Don't free the channel if any of the flags were set. + */ + + if (flags) { + return errorCode; + } + + /* + * Remove the file from the list of watched files. + */ + + for (nextPtrPtr = &(tsdPtr->firstPipePtr), infoPtr = *nextPtrPtr; + infoPtr != NULL; + nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) { + if (infoPtr == (PipeInfo *)pipePtr) { + *nextPtrPtr = infoPtr->nextPtr; + break; + } + } + + if ((pipePtr->flags & PIPE_ASYNC) || inExit) { + /* + * If the channel is non-blocking or Tcl is being cleaned up, just + * detach the children PIDs, reap them (important if we are in a + * dynamic load module), and discard the errorFile. + */ + + Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr); + Tcl_ReapDetachedProcs(); + + if (pipePtr->errorFile) { + if (TclpCloseFile(pipePtr->errorFile) != 0) { + if (errorCode == 0) { + errorCode = errno; + } + } + } + result = 0; + } else { + /* + * Wrap the error file into a channel and give it to the cleanup + * routine. + */ + + if (pipePtr->errorFile) { + WinFile *filePtr = (WinFile *) pipePtr->errorFile; + + errChan = Tcl_MakeFileChannel((void *)filePtr->handle, + TCL_READABLE); + Tcl_Free(filePtr); + Tcl_SetChannelOption(NULL, errChan, "-profile", "replace"); + } else { + errChan = NULL; + } + + result = TclCleanupChildren(interp, pipePtr->numPids, + pipePtr->pidPtr, errChan); + } + + if (pipePtr->numPids > 0) { + Tcl_Free(pipePtr->pidPtr); + } + + if (pipePtr->writeBuf != NULL) { + Tcl_Free(pipePtr->writeBuf); + } + + Tcl_Free(pipePtr); + + if (errorCode == 0) { + return result; + } + return errorCode; +} + +/* + *---------------------------------------------------------------------- + * + * PipeInputProc -- + * + * Reads input from the IO channel into the buffer given. Returns count + * of how many bytes were actually read, and an error indication. + * + * Results: + * A count of how many bytes were read is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Reads input from the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +PipeInputProc( + void *instanceData, /* Pipe state. */ + char *buf, /* Where to store data read. */ + int bufSize, /* How much space is available in the + * buffer? */ + int *errorCode) /* Where to store error code. */ +{ + PipeInfo *infoPtr = (PipeInfo *) instanceData; + WinFile *filePtr = (WinFile*) infoPtr->readFile; + DWORD count, bytesRead = 0; + int result; + + *errorCode = 0; + /* + * Synchronize with the reader thread. + */ + + result = WaitForRead(infoPtr, (infoPtr->flags & PIPE_ASYNC) ? 0 : 1); + + /* + * If an error occurred, return immediately. + */ + + if (result == -1) { + *errorCode = errno; + return -1; + } + + if (infoPtr->readFlags & PIPE_EXTRABYTE) { + /* + * The reader thread consumed 1 byte as a side effect of waiting so we + * need to move it into the buffer. + */ + + *buf = infoPtr->extraByte; + infoPtr->readFlags &= ~PIPE_EXTRABYTE; + buf++; + bufSize--; + bytesRead = 1; + + /* + * If further read attempts would block, return what we have. + */ + + if (result == 0) { + return bytesRead; + } + } + + /* + * Attempt to read bufSize bytes. The read will return immediately if + * there is any data available. Otherwise it will block until at least one + * byte is available or an EOF occurs. + */ + + if (ReadFile(filePtr->handle, (LPVOID) buf, (DWORD) bufSize, &count, + (LPOVERLAPPED) NULL) == TRUE) { + return bytesRead + count; + } else if (bytesRead) { + /* + * Ignore errors if we have data to return. + */ + + return bytesRead; + } + + Tcl_WinConvertError(GetLastError()); + if (errno == EPIPE) { + infoPtr->readFlags |= PIPE_EOF; + return 0; + } + *errorCode = errno; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * PipeOutputProc -- + * + * Writes the given output on the IO channel. Returns count of how many + * characters were actually written, and an error indication. + * + * Results: + * A count of how many characters were written is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Writes output on the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +PipeOutputProc( + void *instanceData, /* Pipe state. */ + const char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCode) /* Where to store error code. */ +{ + PipeInfo *infoPtr = (PipeInfo *) instanceData; + WinFile *filePtr = (WinFile*) infoPtr->writeFile; + DWORD bytesWritten, timeout; + + *errorCode = 0; + + /* avoid blocking if pipe-thread exited */ + timeout = ((infoPtr->flags & PIPE_ASYNC) + || !TclPipeThreadIsAlive(&infoPtr->writeTI) + || TclInExit() || TclInThreadExit()) ? 0 : INFINITE; + if (WaitForSingleObject(infoPtr->writable, timeout) == WAIT_TIMEOUT) { + /* + * The writer thread is blocked waiting for a write to complete and + * the channel is in non-blocking mode. + */ + + errno = EWOULDBLOCK; + goto error; + } + + /* + * Check for a background error on the last write. + */ + + if (infoPtr->writeError) { + Tcl_WinConvertError(infoPtr->writeError); + infoPtr->writeError = 0; + goto error; + } + + if (infoPtr->flags & PIPE_ASYNC) { + /* + * The pipe is non-blocking, so copy the data into the output buffer + * and restart the writer thread. + */ + + if (toWrite > infoPtr->writeBufLen) { + /* + * Reallocate the buffer to be large enough to hold the data. + */ + + if (infoPtr->writeBuf) { + Tcl_Free(infoPtr->writeBuf); + } + infoPtr->writeBufLen = toWrite; + infoPtr->writeBuf = (char *)Tcl_Alloc(toWrite); + } + memcpy(infoPtr->writeBuf, buf, toWrite); + infoPtr->toWrite = toWrite; + ResetEvent(infoPtr->writable); + TclPipeThreadSignal(&infoPtr->writeTI); + bytesWritten = toWrite; + } else { + /* + * In the blocking case, just try to write the buffer directly. This + * avoids an unnecessary copy. + */ + + if (WriteFile(filePtr->handle, (LPVOID) buf, (DWORD) toWrite, + &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) { + Tcl_WinConvertError(GetLastError()); + goto error; + } + } + return bytesWritten; + + error: + *errorCode = errno; + return -1; + +} + +/* + *---------------------------------------------------------------------- + * + * PipeEventProc -- + * + * This function is invoked by Tcl_ServiceEvent when a file event reaches + * the front of the event queue. This function invokes Tcl_NotifyChannel + * on the pipe. + * + * Results: + * Returns 1 if the event was handled, meaning it should be removed from + * the queue. Returns 0 if the event was not handled, meaning it should + * stay on the queue. The only time the event isn't handled is if the + * TCL_FILE_EVENTS flag bit isn't set. + * + * Side effects: + * Whatever the notifier callback does. + * + *---------------------------------------------------------------------- + */ + +static int +PipeEventProc( + Tcl_Event *evPtr, /* Event to service. */ + int flags) /* Flags that indicate what events to + * handle, such as TCL_FILE_EVENTS. */ +{ + PipeEvent *pipeEvPtr = (PipeEvent *)evPtr; + PipeInfo *infoPtr; + int mask; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!(flags & TCL_FILE_EVENTS)) { + return 0; + } + + /* + * Search through the list of watched pipes for the one whose handle + * matches the event. We do this rather than simply dereferencing the + * handle in the event so that pipes can be deleted while the event is in + * the queue. + */ + + for (infoPtr = tsdPtr->firstPipePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (pipeEvPtr->infoPtr == infoPtr) { + infoPtr->flags &= ~(PIPE_PENDING); + break; + } + } + + /* + * Remove stale events. + */ + + if (!infoPtr) { + return 1; + } + + /* + * Check to see if the pipe is readable. Note that we can't tell if a pipe + * is writable, so we always report it as being writable unless we have + * detected EOF. + */ + + mask = 0; + if ((infoPtr->watchMask & TCL_WRITABLE) && + (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT)) { + mask = TCL_WRITABLE; + } + + if ((infoPtr->watchMask & TCL_READABLE) && (WaitForRead(infoPtr,0) >= 0)) { + if (infoPtr->readFlags & PIPE_EOF) { + mask = TCL_READABLE; + } else { + mask |= TCL_READABLE; + } + } + + /* + * Inform the channel of the events. + */ + + Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask); + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * PipeWatchProc -- + * + * Called by the notifier to set up to watch for events on this channel. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +PipeWatchProc( + void *instanceData, /* Pipe state. */ + int mask) /* What events to watch for, OR-ed combination + * of TCL_READABLE, TCL_WRITABLE and + * TCL_EXCEPTION. */ +{ + PipeInfo **nextPtrPtr, *ptr; + PipeInfo *infoPtr = (PipeInfo *) instanceData; + int oldMask = infoPtr->watchMask; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * Since most of the work is handled by the background threads, we just + * need to update the watchMask and then force the notifier to poll once. + */ + + infoPtr->watchMask = mask & infoPtr->validMask; + if (infoPtr->watchMask) { + Tcl_Time blockTime = { 0, 0 }; + + if (!oldMask) { + infoPtr->nextPtr = tsdPtr->firstPipePtr; + tsdPtr->firstPipePtr = infoPtr; + } + Tcl_SetMaxBlockTime(&blockTime); + } else { + if (oldMask) { + /* + * Remove the pipe from the list of watched pipes. + */ + + for (nextPtrPtr = &(tsdPtr->firstPipePtr), ptr = *nextPtrPtr; + ptr != NULL; + nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { + if (infoPtr == ptr) { + *nextPtrPtr = ptr->nextPtr; + break; + } + } + } + } +} + +/* + *---------------------------------------------------------------------- + * + * PipeGetHandleProc -- + * + * Called from Tcl_GetChannelHandle to retrieve OS handles from inside a + * command pipeline based channel. + * + * Results: + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no + * handle for the specified direction. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +PipeGetHandleProc( + void *instanceData, /* The pipe state. */ + int direction, /* TCL_READABLE or TCL_WRITABLE */ + void **handlePtr) /* Where to store the handle. */ +{ + PipeInfo *infoPtr = (PipeInfo *) instanceData; + WinFile *filePtr; + + if (direction == TCL_READABLE && infoPtr->readFile) { + filePtr = (WinFile*) infoPtr->readFile; + *handlePtr = (void *)filePtr->handle; + return TCL_OK; + } + if (direction == TCL_WRITABLE && infoPtr->writeFile) { + filePtr = (WinFile*) infoPtr->writeFile; + *handlePtr = (void *)filePtr->handle; + return TCL_OK; + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_WaitPid -- + * + * Emulates the waitpid system call. + * + * Results: + * Returns 0 if the process is still alive, -1 on an error, or the pid on + * a clean close. + * + * Side effects: + * Unless WNOHANG is set and the wait times out, the process information + * record will be deleted and the process handle will be closed. + * + *---------------------------------------------------------------------- + */ + +Tcl_Pid +Tcl_WaitPid( + Tcl_Pid pid, + int *statPtr, + int options) +{ + ProcInfo *infoPtr = NULL, **prevPtrPtr; + DWORD flags; + Tcl_Pid result; + DWORD ret, exitCode; + + PipeInit(); + + /* + * If no pid is specified, do nothing. + */ + + if (pid == 0) { + *statPtr = 0; + return 0; + } + + /* + * Find the process and cut it from the process list. + */ + + Tcl_MutexLock(&pipeMutex); + prevPtrPtr = &procList; + for (infoPtr = procList; infoPtr != NULL; + prevPtrPtr = &infoPtr->nextPtr, infoPtr = infoPtr->nextPtr) { + if (infoPtr->dwProcessId == (Tcl_Size)pid) { + *prevPtrPtr = infoPtr->nextPtr; + break; + } + } + Tcl_MutexUnlock(&pipeMutex); + + /* + * If the pid is not one of the processes we know about (we started it) + * then do nothing. + */ + + if (infoPtr == NULL) { + *statPtr = 0; + return 0; + } + + /* + * Officially "wait" for it to finish. We either poll (WNOHANG) or wait + * for an infinite amount of time. + */ + + if (options & WNOHANG) { + flags = 0; + } else { + flags = INFINITE; + } + ret = WaitForSingleObject(infoPtr->hProcess, flags); + if (ret == WAIT_TIMEOUT) { + *statPtr = 0; + if (options & WNOHANG) { + /* + * Re-insert this infoPtr back on the list. + */ + + Tcl_MutexLock(&pipeMutex); + infoPtr->nextPtr = procList; + procList = infoPtr; + Tcl_MutexUnlock(&pipeMutex); + return 0; + } else { + result = 0; + } + } else if (ret == WAIT_OBJECT_0) { + GetExitCodeProcess(infoPtr->hProcess, &exitCode); + + /* + * Does the exit code look like one of the exception codes? + */ + + switch (exitCode) { + case EXCEPTION_FLT_DENORMAL_OPERAND: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INEXACT_RESULT: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_FLT_STACK_CHECK: + case EXCEPTION_FLT_UNDERFLOW: + case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_INT_OVERFLOW: + *statPtr = 0xC0000000 | SIGFPE; + break; + + case EXCEPTION_PRIV_INSTRUCTION: + case EXCEPTION_ILLEGAL_INSTRUCTION: + *statPtr = 0xC0000000 | SIGILL; + break; + + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + case EXCEPTION_STACK_OVERFLOW: + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + case EXCEPTION_INVALID_DISPOSITION: + case EXCEPTION_GUARD_PAGE: + case EXCEPTION_INVALID_HANDLE: + *statPtr = 0xC0000000 | SIGSEGV; + break; + + case EXCEPTION_DATATYPE_MISALIGNMENT: + *statPtr = 0xC0000000 | SIGBUS; + break; + + case EXCEPTION_BREAKPOINT: + case EXCEPTION_SINGLE_STEP: + *statPtr = 0xC0000000 | SIGTRAP; + break; + + case CONTROL_C_EXIT: + *statPtr = 0xC0000000 | SIGINT; + break; + + default: + /* + * Non-exceptional, normal, exit code. Note that the exit code is + * truncated to a signed short range [-32768,32768) whether it + * fits into this range or not. + * + * BUG: Even though the exit code is a DWORD, it is understood by + * convention to be a signed integer, yet there isn't enough room + * to fit this into the POSIX style waitstatus mask without + * truncating it. + */ + + *statPtr = exitCode; + break; + } + result = pid; + } else { + errno = ECHILD; + *statPtr = 0xC0000000 | ECHILD; + result = (Tcl_Pid)-1; + } + + /* + * Officially close the process handle. + */ + + CloseHandle(infoPtr->hProcess); + Tcl_Free(infoPtr); + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclWinAddProcess -- + * + * Add a process to the process list so that we can use Tcl_WaitPid on + * the process. + * + * Results: + * None + * + * Side effects: + * Adds the specified process handle to the process list so Tcl_WaitPid + * knows about it. + * + *---------------------------------------------------------------------- + */ + +void +TclWinAddProcess( + void *hProcess, /* Handle to process */ + Tcl_Size id) /* Global process identifier */ +{ + ProcInfo *procPtr = (ProcInfo *)Tcl_Alloc(sizeof(ProcInfo)); + + PipeInit(); + + procPtr->hProcess = hProcess; + procPtr->dwProcessId = id; + Tcl_MutexLock(&pipeMutex); + procPtr->nextPtr = procList; + procList = procPtr; + Tcl_MutexUnlock(&pipeMutex); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_PidObjCmd -- + * + * This function is invoked to process the "pid" Tcl command. See the + * user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_PidObjCmd( + TCL_UNUSED(void *), + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const *objv) /* Argument strings. */ +{ + Tcl_Channel chan; + const Tcl_ChannelType *chanTypePtr; + PipeInfo *pipePtr; + size_t i; + Tcl_Obj *resultPtr; + + if (objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "?channel?"); + return TCL_ERROR; + } + if (objc == 1) { + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(getpid())); + } else { + chan = Tcl_GetChannel(interp, TclGetString(objv[1]), + NULL); + if (chan == (Tcl_Channel) NULL) { + return TCL_ERROR; + } + chanTypePtr = Tcl_GetChannelType(chan); + if (chanTypePtr != &pipeChannelType) { + return TCL_OK; + } + + pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan); + TclNewObj(resultPtr); + for (i = 0; i < pipePtr->numPids; i++) { + Tcl_ListObjAppendElement(/*interp*/ NULL, resultPtr, + Tcl_NewWideIntObj( + TclpGetPid(pipePtr->pidPtr[i]))); + } + Tcl_SetObjResult(interp, resultPtr); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * WaitForRead -- + * + * Wait until some data is available, the pipe is at EOF or the reader + * thread is blocked waiting for data (if the channel is in non-blocking + * mode). + * + * Results: + * Returns 1 if pipe is readable. Returns 0 if there is no data on the + * pipe, but there is buffered data. Returns -1 if an error occurred. If + * an error occurred, the threads may not be synchronized. + * + * Side effects: + * Updates the shared state flags and may consume 1 byte of data from the + * pipe. If no error occurred, the reader thread is blocked waiting for a + * signal from the main thread. + * + *---------------------------------------------------------------------- + */ + +static int +WaitForRead( + PipeInfo *infoPtr, /* Pipe state. */ + int blocking) /* Indicates whether call should be blocking + * or not. */ +{ + DWORD timeout, count; + HANDLE handle = ((WinFile *) infoPtr->readFile)->handle; + + while (1) { + /* + * Synchronize with the reader thread. + */ + + /* avoid blocking if pipe-thread exited */ + timeout = (!blocking || !TclPipeThreadIsAlive(&infoPtr->readTI) + || TclInExit() || TclInThreadExit()) ? 0 : INFINITE; + if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) { + /* + * The reader thread is blocked waiting for data and the channel + * is in non-blocking mode. + */ + + errno = EWOULDBLOCK; + return -1; + } + + /* + * At this point, the two threads are synchronized, so it is safe to + * access shared state. + */ + + /* + * If the pipe has hit EOF, it is always readable. + */ + + if (infoPtr->readFlags & PIPE_EOF) { + return 1; + } + + /* + * Check to see if there is any data sitting in the pipe. + */ + + if (PeekNamedPipe(handle, (LPVOID) NULL, (DWORD) 0, + (LPDWORD) NULL, &count, (LPDWORD) NULL) != TRUE) { + Tcl_WinConvertError(GetLastError()); + + /* + * Check to see if the peek failed because of EOF. + */ + + if (errno == EPIPE) { + infoPtr->readFlags |= PIPE_EOF; + return 1; + } + + /* + * Ignore errors if there is data in the buffer. + */ + + if (infoPtr->readFlags & PIPE_EXTRABYTE) { + return 0; + } else { + return -1; + } + } + + /* + * We found some data in the pipe, so it must be readable. + */ + + if (count > 0) { + return 1; + } + + /* + * The pipe isn't readable, but there is some data sitting in the + * buffer, so return immediately. + */ + + if (infoPtr->readFlags & PIPE_EXTRABYTE) { + return 0; + } + + /* + * There wasn't any data available, so reset the thread and try again. + */ + + ResetEvent(infoPtr->readable); + TclPipeThreadSignal(&infoPtr->readTI); + } +} + +/* + *---------------------------------------------------------------------- + * + * PipeReaderThread -- + * + * This function runs in a separate thread and waits for input to become + * available on a pipe. + * + * Results: + * None. + * + * Side effects: + * Signals the main thread when input become available. May cause the + * main thread to wake up by posting a message. May consume one byte from + * the pipe for each wait operation. Will cause a memory leak of ~4k, if + * forcefully terminated with TerminateThread(). + * + *---------------------------------------------------------------------- + */ + +static DWORD WINAPI +PipeReaderThread( + LPVOID arg) +{ + TclPipeThreadInfo *pipeTI = (TclPipeThreadInfo *) arg; + PipeInfo *infoPtr = NULL; /* access info only after success init/wait */ + HANDLE handle = NULL; + DWORD count, err; + int done = 0; + + while (!done) { + /* + * Wait for the main thread to signal before attempting to wait on the + * pipe becoming readable. + */ + + if (!TclPipeThreadWaitForSignal(&pipeTI)) { + /* exit */ + break; + } + + if (!infoPtr) { + infoPtr = (PipeInfo *) pipeTI->clientData; + handle = ((WinFile *) infoPtr->readFile)->handle; + } + + /* + * Try waiting for 0 bytes. This will block until some data is + * available on NT, but will return immediately on Win 95. So, if no + * data is available after the first read, we block until we can read + * a single byte off of the pipe. + */ + + if (ReadFile(handle, NULL, 0, &count, NULL) == FALSE || + PeekNamedPipe(handle, NULL, 0, NULL, &count, NULL) == FALSE) { + /* + * The error is a result of an EOF condition, so set the EOF bit + * before signalling the main thread. + */ + + err = GetLastError(); + if (err == ERROR_BROKEN_PIPE) { + infoPtr->readFlags |= PIPE_EOF; + done = 1; + } else if (err == ERROR_INVALID_HANDLE) { + done = 1; + } + } else if (count == 0) { + if (ReadFile(handle, &(infoPtr->extraByte), 1, &count, NULL) + != FALSE) { + /* + * One byte was consumed as a side effect of waiting for the + * pipe to become readable. + */ + + infoPtr->readFlags |= PIPE_EXTRABYTE; + } else { + err = GetLastError(); + if (err == ERROR_BROKEN_PIPE) { + /* + * The error is a result of an EOF condition, so set the + * EOF bit before signalling the main thread. + */ + + infoPtr->readFlags |= PIPE_EOF; + done = 1; + } else if (err == ERROR_INVALID_HANDLE) { + done = 1; + } + } + } + + /* + * Signal the main thread by signalling the readable event and then + * waking up the notifier thread. + */ + + SetEvent(infoPtr->readable); + + /* + * Alert the foreground thread. Note that we need to treat this like a + * critical section so the foreground thread does not terminate this + * thread while we are holding a mutex in the notifier code. + */ + + Tcl_MutexLock(&pipeMutex); + if (infoPtr->threadId != NULL) { + /* + * TIP #218. When in flight ignore the event, no one will receive + * it anyway. + */ + + Tcl_ThreadAlert(infoPtr->threadId); + } + Tcl_MutexUnlock(&pipeMutex); + } + + /* + * If state of thread was set to stop, we can sane free info structure, + * otherwise it is shared with main thread, so main thread will own it + */ + TclPipeThreadExit(&pipeTI); + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * PipeWriterThread -- + * + * This function runs in a separate thread and writes data onto a pipe. + * + * Results: + * Always returns 0. + * + * Side effects: + * Signals the main thread when an output operation is completed. May + * cause the main thread to wake up by posting a message. + * + *---------------------------------------------------------------------- + */ + +static DWORD WINAPI +PipeWriterThread( + LPVOID arg) +{ + TclPipeThreadInfo *pipeTI = (TclPipeThreadInfo *)arg; + PipeInfo *infoPtr = NULL; /* access info only after success init/wait */ + HANDLE handle = NULL; + DWORD count, toWrite; + char *buf; + int done = 0; + + while (!done) { + /* + * Wait for the main thread to signal before attempting to write. + */ + if (!TclPipeThreadWaitForSignal(&pipeTI)) { + /* exit */ + break; + } + + if (!infoPtr) { + infoPtr = (PipeInfo *)pipeTI->clientData; + handle = ((WinFile *) infoPtr->writeFile)->handle; + } + + buf = infoPtr->writeBuf; + toWrite = infoPtr->toWrite; + + /* + * Loop until all of the bytes are written or an error occurs. + */ + + while (toWrite > 0) { + if (WriteFile(handle, buf, toWrite, &count, NULL) == FALSE) { + infoPtr->writeError = GetLastError(); + done = 1; + break; + } else { + toWrite -= count; + buf += count; + } + } + + /* + * Signal the main thread by signalling the writable event and then + * waking up the notifier thread. + */ + + SetEvent(infoPtr->writable); + + /* + * Alert the foreground thread. Note that we need to treat this like a + * critical section so the foreground thread does not terminate this + * thread while we are holding a mutex in the notifier code. + */ + + Tcl_MutexLock(&pipeMutex); + if (infoPtr->threadId != NULL) { + /* + * TIP #218. When in flight ignore the event, no one will receive + * it anyway. + */ + + Tcl_ThreadAlert(infoPtr->threadId); + } + Tcl_MutexUnlock(&pipeMutex); + } + + /* + * If state of thread was set to stop, we can sane free info structure, + * otherwise it is shared with main thread, so main thread will own it. + */ + TclPipeThreadExit(&pipeTI); + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * PipeThreadActionProc -- + * + * Insert or remove any thread local refs to this channel. + * + * Results: + * None. + * + * Side effects: + * Changes thread local list of valid channels. + * + *---------------------------------------------------------------------- + */ + +static void +PipeThreadActionProc( + void *instanceData, + int action) +{ + PipeInfo *infoPtr = (PipeInfo *) instanceData; + + /* + * We do not access firstPipePtr in the thread structures. This is not for + * all pipes managed by the thread, but only those we are watching. + * Removal of the fileevent handlers before transfer thus takes care of + * this structure. + */ + + Tcl_MutexLock(&pipeMutex); + if (action == TCL_CHANNEL_THREAD_INSERT) { + /* + * We can't copy the thread information from the channel when the + * channel is created. At this time the channel back pointer has not + * been set yet. However in that case the threadId has already been + * set by TclpCreateCommandChannel itself, so the structure is still + * good. + */ + + PipeInit(); + if (infoPtr->channel != NULL) { + infoPtr->threadId = Tcl_GetChannelThread(infoPtr->channel); + } + } else { + infoPtr->threadId = NULL; + } + Tcl_MutexUnlock(&pipeMutex); +} + +/* + *---------------------------------------------------------------------- + * + * TclpOpenTemporaryFile -- + * + * Creates a temporary file, possibly based on the supplied bits and + * pieces of template supplied in the first three arguments. If the + * fourth argument is non-NULL, it contains a Tcl_Obj to store the name + * of the temporary file in (and it is caller's responsibility to clean + * up). If the fourth argument is NULL, try to arrange for the temporary + * file to go away once it is no longer needed. + * + * Results: + * A read-write Tcl Channel open on the file. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +TclpOpenTemporaryFile( + TCL_UNUSED(Tcl_Obj *) /*dirObj*/, + Tcl_Obj *basenameObj, + TCL_UNUSED(Tcl_Obj *) /*extensionObj*/, + Tcl_Obj *resultingNameObj) +{ + WCHAR name[MAX_PATH]; + char *namePtr; + HANDLE handle; + DWORD flags = FILE_ATTRIBUTE_TEMPORARY; + Tcl_Size length; + int counter, counter2; + Tcl_DString buf; + + if (!resultingNameObj) { + flags |= FILE_FLAG_DELETE_ON_CLOSE; + } + + namePtr = (char *) name; + length = GetTempPathW(MAX_PATH, name); + if (length == 0) { + goto gotError; + } + namePtr += length * sizeof(WCHAR); + if (basenameObj) { + const char *string = TclGetStringFromObj(basenameObj, &length); + + Tcl_DStringInit(&buf); + Tcl_UtfToWCharDString(string, length, &buf); + memcpy(namePtr, Tcl_DStringValue(&buf), Tcl_DStringLength(&buf)); + namePtr += Tcl_DStringLength(&buf); + Tcl_DStringFree(&buf); + } else { + const WCHAR *baseStr = L"TCL"; + length = 3 * sizeof(WCHAR); + + memcpy(namePtr, baseStr, length); + namePtr += length; + } + counter = TclpGetClicks() % 65533; + counter2 = 1024; /* Only try this many times! Prevents + * an infinite loop. */ + + do { + char number[TCL_INTEGER_SPACE + 4]; + + snprintf(number, sizeof(number), "%d.TMP", counter); + counter = (unsigned short) (counter + 1); + Tcl_DStringInit(&buf); + Tcl_UtfToWCharDString(number, strlen(number), &buf); + Tcl_DStringSetLength(&buf, Tcl_DStringLength(&buf) + 1); + memcpy(namePtr, Tcl_DStringValue(&buf), Tcl_DStringLength(&buf) + 1); + Tcl_DStringFree(&buf); + + handle = CreateFileW(name, + GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_NEW, flags, NULL); + } while (handle == INVALID_HANDLE_VALUE + && --counter2 > 0 + && GetLastError() == ERROR_FILE_EXISTS); + if (handle == INVALID_HANDLE_VALUE) { + goto gotError; + } + + if (resultingNameObj) { + Tcl_Obj *tmpObj = TclpNativeToNormalized(name); + + Tcl_AppendObjToObj(resultingNameObj, tmpObj); + TclDecrRefCount(tmpObj); + } + + return Tcl_MakeFileChannel((void *)handle, + TCL_READABLE|TCL_WRITABLE); + + gotError: + Tcl_WinConvertError(GetLastError()); + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * TclPipeThreadCreateTI -- + * + * Creates a thread info structure, can be owned by worker. + * + * Results: + * Pointer to created TI structure. + * + *---------------------------------------------------------------------- + */ + +TclPipeThreadInfo * +TclPipeThreadCreateTI( + TclPipeThreadInfo **pipeTIPtr, + void *clientData) +{ + TclPipeThreadInfo *pipeTI; +#ifndef _PTI_USE_CKALLOC + pipeTI = (TclPipeThreadInfo *)malloc(sizeof(TclPipeThreadInfo)); +#else + pipeTI = (TclPipeThreadInfo *)Tcl_Alloc(sizeof(TclPipeThreadInfo)); +#endif /* !_PTI_USE_CKALLOC */ + pipeTI->evControl = CreateEventW(NULL, FALSE, FALSE, NULL); + pipeTI->state = PTI_STATE_IDLE; + pipeTI->clientData = clientData; + return (*pipeTIPtr = pipeTI); +} + +/* + *---------------------------------------------------------------------- + * + * TclPipeThreadWaitForSignal -- + * + * Wait for work/stop signals inside pipe worker. + * + * Results: + * 1 if signaled to work, 0 if signaled to stop. + * + * Side effects: + * If this function returns 0, TI-structure pointer given via pipeTIPtr + * may be NULL, so not accessible (can be owned by main thread). + * + *---------------------------------------------------------------------- + */ + +int +TclPipeThreadWaitForSignal( + TclPipeThreadInfo **pipeTIPtr) +{ + TclPipeThreadInfo *pipeTI = *pipeTIPtr; + LONG state; + DWORD waitResult; + + if (!pipeTI) { + return 0; + } + + /* + * Wait for the main thread to signal before attempting to do the work. + */ + + /* + * Reset work state of thread (idle/waiting) + */ + + state = InterlockedCompareExchange(&pipeTI->state, PTI_STATE_IDLE, + PTI_STATE_WORK); + if (state & (PTI_STATE_STOP|PTI_STATE_END)) { + /* + * End of work, check the owner of structure. + */ + + goto end; + } + + /* + * Entering wait + */ + + waitResult = WaitForSingleObject(pipeTI->evControl, INFINITE); + if (waitResult != WAIT_OBJECT_0) { + /* + * The control event was not signaled, so end of work (unexpected + * behaviour, main thread can be dead?). + */ + + goto end; + } + + /* + * Try to set work state of thread + */ + + state = InterlockedCompareExchange(&pipeTI->state, PTI_STATE_WORK, + PTI_STATE_IDLE); + if (state & (PTI_STATE_STOP|PTI_STATE_END)) { + /* + * End of work + */ + + goto end; + } + + /* + * Signaled to work. + */ + + return 1; + + end: + /* + * End of work, check the owner of the TI structure. + */ + + if (state != PTI_STATE_STOP) { + *pipeTIPtr = NULL; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclPipeThreadStopSignal -- + * + * Send stop signal to the pipe worker (without waiting). + * + * After calling of this function, TI-structure pointer given via pipeTIPtr + * may be NULL. + * + * Results: + * 1 if signaled (or pipe-thread is down), 0 if pipe thread still working. + * + *---------------------------------------------------------------------- + */ + +int +TclPipeThreadStopSignal( + TclPipeThreadInfo **pipeTIPtr) +{ + TclPipeThreadInfo *pipeTI = *pipeTIPtr; + HANDLE evControl; + int state; + + if (!pipeTI) { + return 1; + } + evControl = pipeTI->evControl; + state = InterlockedCompareExchange(&pipeTI->state, PTI_STATE_STOP, + PTI_STATE_IDLE); + switch (state) { + case PTI_STATE_IDLE: + /* + * Thread was idle/waiting, notify it goes teardown + */ + + SetEvent(evControl); + *pipeTIPtr = NULL; + TCL_FALLTHROUGH(); + case PTI_STATE_DOWN: + return 1; + + default: + /* + * Thread works currently, we should try to end it, own the TI + * structure (because of possible sharing the joint structures with + * thread) + */ + + InterlockedExchange(&pipeTI->state, PTI_STATE_END); + break; + } + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclPipeThreadStop -- + * + * Send stop signal to the pipe worker and wait for thread completion. + * + * May be combined with TclPipeThreadStopSignal. + * + * After calling of this function, TI-structure pointer given via pipeTIPtr + * is not accessible (owned by pipe worker or released here). + * + * Results: + * None. + * + * Side effects: + * Can terminate pipe worker (and / or stop its synchronous operations). + * + *---------------------------------------------------------------------- + */ + +void +TclPipeThreadStop( + TclPipeThreadInfo **pipeTIPtr, + HANDLE hThread) +{ + TclPipeThreadInfo *pipeTI = *pipeTIPtr; + HANDLE evControl; + int state; + + if (!pipeTI) { + return; + } + pipeTI = *pipeTIPtr; + evControl = pipeTI->evControl; + + /* + * Try to sane stop the pipe worker, corresponding its current state + */ + + state = InterlockedCompareExchange(&pipeTI->state, PTI_STATE_STOP, + PTI_STATE_IDLE); + switch (state) { + case PTI_STATE_IDLE: + /* + * Thread was idle/waiting, notify it goes teardown + */ + + SetEvent(evControl); + + /* + * We don't need to wait for it at all, thread frees himself (owns the + * TI structure) + */ + + pipeTI = NULL; + break; + + case PTI_STATE_STOP: + /* + * Already stopped, thread frees himself (owns the TI structure) + */ + + pipeTI = NULL; + break; + case PTI_STATE_DOWN: + /* + * Thread already down (?), do nothing + */ + + /* + * We don't need to wait for it, but we should free pipeTI + */ + hThread = NULL; + break; + + /* case PTI_STATE_WORK: */ + default: + /* + * Thread works currently, we should try to end it, own the TI + * structure (because of possible sharing the joint structures with + * thread) + */ + + state = InterlockedCompareExchange(&pipeTI->state, PTI_STATE_END, + PTI_STATE_WORK); + if (state == PTI_STATE_DOWN) { + /* + * We don't need to wait for it, but we should free pipeTI + */ + hThread = NULL; + } + break; + } + + if (pipeTI && hThread) { + DWORD exitCode; + + /* + * The thread may already have closed on its own. Check its exit + * code. + */ + + GetExitCodeThread(hThread, &exitCode); + + if (exitCode == STILL_ACTIVE) { + int inExit = (TclInExit() || TclInThreadExit()); + + /* + * Set the stop event so that if the pipe thread is blocked + * somewhere, it may hereafter sane exit cleanly. + */ + + SetEvent(evControl); + + /* + * Cancel all sync-IO of this thread (may be blocked there). + */ + + CancelSynchronousIo(hThread); + + /* + * Wait at most 20 milliseconds for the reader thread to close + * (regarding TIP#398-fast-exit). + */ + + /* + * If we want TIP#398-fast-exit. + */ + + if (WaitForSingleObject(hThread, inExit ? 0 : 20) == WAIT_TIMEOUT) { + /* + * The thread must be blocked waiting for the pipe to become + * readable in ReadFile(). There isn't a clean way to exit the + * thread from this condition. We should terminate the child + * process instead to get the reader thread to fall out of + * ReadFile with a FALSE. (below) is not the correct way to do + * this, but will stay here until a better solution is found. + * + * Note that we need to guard against terminating the thread + * while it is in the middle of Tcl_ThreadAlert because it + * won't be able to release the notifier lock. + * + * Also note that terminating threads during their + * initialization or teardown phase may result in ntdll.dll's + * LoaderLock to remain locked indefinitely. This causes + * ntdll.dll's LdrpInitializeThread() to deadlock trying to + * acquire LoaderLock. LdrpInitializeThread() is executed + * within new threads to perform initialization and to execute + * DllMain() of all loaded dlls. As a result, all new threads + * are deadlocked in their initialization phase and never + * execute, even though CreateThread() reports successful + * thread creation. This results in a very weird process-wide + * behavior, which is extremely hard to debug. + * + * THREADS SHOULD NEVER BE TERMINATED. Period. + * + * But for now, check if thread is exiting, and if so, let it + * die peacefully. + * + * Also don't terminate if in exit (otherwise deadlocked in + * ntdll.dll's). + */ + + if (pipeTI->state != PTI_STATE_DOWN + && WaitForSingleObject(hThread, + inExit ? 50 : 5000) != WAIT_OBJECT_0) { + /* BUG: this leaks memory */ + if (inExit || !TerminateThread(hThread, 0)) { + /* + * in exit or terminate fails, just give thread a + * chance to exit + */ + + if (InterlockedExchange(&pipeTI->state, + PTI_STATE_STOP) != PTI_STATE_DOWN) { + pipeTI = NULL; + } + } + } + } + } + } + + *pipeTIPtr = NULL; + if (pipeTI) { + CloseHandle(pipeTI->evControl); +#ifndef _PTI_USE_CKALLOC + free(pipeTI); +#else + Tcl_Free(pipeTI); +#endif /* !_PTI_USE_CKALLOC */ + } +} + +/* + *---------------------------------------------------------------------- + * + * TclPipeThreadExit -- + * + * Clean-up for the pipe thread (removes owned TI-structure in worker). + * + * Should be executed on worker exit, to inform the main thread or + * free TI-structure (if owned). + * + * After calling of this function, TI-structure pointer given via pipeTIPtr + * is not accessible (owned by main thread or released here). + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclPipeThreadExit( + TclPipeThreadInfo **pipeTIPtr) +{ + LONG state; + TclPipeThreadInfo *pipeTI = *pipeTIPtr; + + /* + * If state of thread was set to stop (exactly), we can sane free its info + * structure, otherwise it is shared with main thread, so main thread will + * own it. + */ + + if (!pipeTI) { + return; + } + *pipeTIPtr = NULL; + state = InterlockedExchange(&pipeTI->state, PTI_STATE_DOWN); + if (state == PTI_STATE_STOP) { + CloseHandle(pipeTI->evControl); +#ifndef _PTI_USE_CKALLOC + free(pipeTI); +#else + Tcl_Free(pipeTI); + /* be sure all subsystems used are finalized */ + Tcl_FinalizeThread(); +#endif /* !_PTI_USE_CKALLOC */ + } +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclWinPort.h b/vendor/tcl/win/tclWinPort.h new file mode 100644 index 00000000..afb76dfa --- /dev/null +++ b/vendor/tcl/win/tclWinPort.h @@ -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 +#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 +#include +#ifdef HAVE_WSPIAPI_H +# include +#endif + +/* + * Pull in the typedef of TCHAR for windows. + */ +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_INTTYPES_H +# include +#endif +#include +#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 +#include +#include +#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 */ diff --git a/vendor/tcl/win/tclWinReg.c b/vendor/tcl/win/tclWinReg.c new file mode 100644 index 00000000..6b8d3f38 --- /dev/null +++ b/vendor/tcl/win/tclWinReg.c @@ -0,0 +1,1598 @@ +/* + * tclWinReg.c -- + * + * This file contains the implementation of the "registry" Tcl built-in + * command. This command is built as a dynamically loadable extension in + * a separate DLL. + * + * Copyright (c) 1997 by Sun Microsystems, Inc. + * Copyright (c) 1998-1999 by Scriptics Corporation. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#undef STATIC_BUILD +#ifndef USE_TCL_STUBS +# define USE_TCL_STUBS +#endif +#include "tclInt.h" +#ifdef _MSC_VER +# pragma comment (lib, "advapi32.lib") +#endif +#include +#if defined (__clang__) && (__clang_major__ > 20) +#pragma clang diagnostic ignored "-Wc++-keyword" +#endif + +/* + * Ensure that we can say which registry is being accessed. + */ + +#ifndef KEY_WOW64_64KEY +# define KEY_WOW64_64KEY (0x0100) +#endif +#ifndef KEY_WOW64_32KEY +# define KEY_WOW64_32KEY (0x0200) +#endif + +/* + * The maximum length of a sub-key name. + */ + +#ifndef MAX_KEY_LENGTH +# define MAX_KEY_LENGTH 256 +#endif + +/* + * The following macros convert between different endian ints. + */ + +#define SWAPWORD(x) MAKEWORD(HIBYTE(x), LOBYTE(x)) +#define SWAPLONG(x) MAKELONG(SWAPWORD(HIWORD(x)), SWAPWORD(LOWORD(x))) + +/* + * The following flag is used in OpenKeys to indicate that the specified key + * should be created if it doesn't currently exist. + */ +enum OpenKeysFlags { + REG_CREATE = 1 +}; + +/* + * The following tables contain the mapping from registry root names to the + * system predefined keys. + */ + +static const char *const rootKeyNames[] = { + "HKEY_LOCAL_MACHINE", "HKEY_USERS", "HKEY_CLASSES_ROOT", + "HKEY_CURRENT_USER", "HKEY_CURRENT_CONFIG", + "HKEY_PERFORMANCE_DATA", "HKEY_DYN_DATA", NULL +}; + +static const HKEY rootKeys[] = { + HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, + HKEY_CURRENT_CONFIG, HKEY_PERFORMANCE_DATA, HKEY_DYN_DATA +}; + +static const char REGISTRY_ASSOC_KEY[] = "registry::command"; + +/* + * The following table maps from registry types to strings. Note that the + * indices for this array are the same as the constants for the known registry + * types so we don't need a separate table to hold the mapping. + */ + +static const char *const typeNames[] = { + "none", "sz", "expand_sz", "binary", "dword", + "dword_big_endian", "link", "multi_sz", "resource_list", NULL +}; + +static DWORD lastType = REG_RESOURCE_LIST; + +#if TCL_MAJOR_VERSION < 9 +# if TCL_UTF_MAX > 3 +# define Tcl_WCharToUtfDString(a,b,c) Tcl_WinTCharToUtf((TCHAR *)(a),(b)*sizeof(WCHAR),c) +# define Tcl_UtfToWCharDString(a,b,c) (WCHAR *)Tcl_WinUtfToTChar(a,b,c) +# else +# define Tcl_WCharToUtfDString(a,b,c) Tcl_UniCharToUtfDString((Tcl_UniChar *)(a),b,c) +# define Tcl_UtfToWCharDString(a,b,c) (WCHAR *)Tcl_UtfToUniCharDString(a,b,c) +# endif +#ifndef Tcl_Size +# define Tcl_Size int +#endif +#ifndef Tcl_CreateObjCommand2 +# define Tcl_CreateObjCommand2 Tcl_CreateObjCommand +#endif +#endif + +/* + * Declarations for functions defined in this file. + */ + +static void AppendSystemError(Tcl_Interp *interp, DWORD error); +static int BroadcastValue(Tcl_Interp *interp, Tcl_Size objc, + Tcl_Obj *const objv[]); +static DWORD ConvertDWORD(DWORD type, DWORD value); +static void DeleteCmd(void *clientData); +static int DeleteKey(Tcl_Interp *interp, Tcl_Obj *keyNameObj, + REGSAM mode); +static int DeleteValue(Tcl_Interp *interp, Tcl_Obj *keyNameObj, + Tcl_Obj *valueNameObj, REGSAM mode); +static int GetKeyNames(Tcl_Interp *interp, Tcl_Obj *keyNameObj, + Tcl_Obj *patternObj, REGSAM mode); +static int GetType(Tcl_Interp *interp, Tcl_Obj *keyNameObj, + Tcl_Obj *valueNameObj, REGSAM mode); +static int GetValue(Tcl_Interp *interp, Tcl_Obj *keyNameObj, + Tcl_Obj *valueNameObj, REGSAM mode); +static int GetValueNames(Tcl_Interp *interp, Tcl_Obj *keyNameObj, + Tcl_Obj *patternObj, REGSAM mode); +static int OpenKey(Tcl_Interp *interp, Tcl_Obj *keyNameObj, + REGSAM mode, int flags, HKEY *keyPtr); +static DWORD OpenSubKey(char *hostName, HKEY rootKey, + char *keyName, REGSAM mode, int flags, + HKEY *keyPtr); +static int ParseKeyName(Tcl_Interp *interp, char *name, + char **hostNamePtr, HKEY *rootKeyPtr, + char **keyNamePtr); +static DWORD RecursiveDeleteKey(HKEY hStartKey, + const WCHAR * pKeyName, REGSAM mode); +static int RegistryObjCmd(void *clientData, + Tcl_Interp *interp, Tcl_Size objc, + Tcl_Obj *const objv[]); +static int SetValue(Tcl_Interp *interp, Tcl_Obj *keyNameObj, + Tcl_Obj *valueNameObj, Tcl_Obj *dataObj, + Tcl_Obj *typeObj, REGSAM mode); + +#ifdef __cplusplus +extern "C" { +#endif +DLLEXPORT int Registry_Init(Tcl_Interp *interp); +DLLEXPORT int Registry_Unload(Tcl_Interp *interp, int flags); +#if TCL_MAJOR_VERSION < 9 +/* With those additional entries, "load tclregistry13.dll" works without 3th argument */ +DLLEXPORT int Tclregistry_Init(Tcl_Interp *interp); +DLLEXPORT int Tclregistry_Unload(Tcl_Interp *interp, int flags); +#endif +#ifdef __cplusplus +} +#endif + +/* + *---------------------------------------------------------------------- + * + * Registry_Init -- + * + * This function initializes the registry command. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +Registry_Init( + Tcl_Interp *interp) +{ + Tcl_Command cmd; + + if (Tcl_InitStubs(interp, "8.5-", 0) == NULL) { + return TCL_ERROR; + } + + cmd = Tcl_CreateObjCommand2(interp, "registry", RegistryObjCmd, + interp, DeleteCmd); + Tcl_SetAssocData(interp, REGISTRY_ASSOC_KEY, NULL, cmd); + return Tcl_PkgProvideEx(interp, "registry", "1.3.7", NULL); +} +#if TCL_MAJOR_VERSION < 9 +int +Tclregistry_Init( + Tcl_Interp *interp) +{ + return Registry_Init(interp); +} +#endif + +/* + *---------------------------------------------------------------------- + * + * Registry_Unload -- + * + * This function removes the registry command. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * The registry command is deleted and the dll may be unloaded. + * + *---------------------------------------------------------------------- + */ + +int +Registry_Unload( + Tcl_Interp *interp, /* Interpreter for unloading */ + int flags) /* Flags passed by the unload system */ +{ + Tcl_Command cmd; + Tcl_Obj *objv[3]; + (void)flags; + + /* + * Unregister the registry package. There is no Tcl_PkgForget() + */ + + objv[0] = Tcl_NewStringObj("package", -1); + objv[1] = Tcl_NewStringObj("forget", -1); + objv[2] = Tcl_NewStringObj("registry", -1); + Tcl_EvalObjv(interp, 3, objv, TCL_EVAL_GLOBAL); + + /* + * Delete the originally registered command. + */ + + cmd = (Tcl_Command)Tcl_GetAssocData(interp, REGISTRY_ASSOC_KEY, NULL); + if (cmd != NULL) { + Tcl_DeleteCommandFromToken(interp, cmd); + } + + return TCL_OK; +} +#if TCL_MAJOR_VERSION < 9 +int +Tclregistry_Unload( + Tcl_Interp *interp, + int flags) +{ + return Registry_Unload(interp, flags); +} +#endif + +/* + *---------------------------------------------------------------------- + * + * DeleteCmd -- + * + * Cleanup the interp command token so that unloading doesn't try to + * re-delete the command (which will crash). + * + * Results: + * None. + * + * Side effects: + * The unload command will not attempt to delete this command. + * + *---------------------------------------------------------------------- + */ + +static void +DeleteCmd( + void *clientData) +{ + Tcl_Interp *interp = (Tcl_Interp *)clientData; + + Tcl_SetAssocData(interp, REGISTRY_ASSOC_KEY, NULL, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * RegistryObjCmd -- + * + * This function implements the Tcl "registry" command. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +RegistryObjCmd( + void *dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Size objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument values. */ +{ + Tcl_Size n = 1, argc; + int index; + REGSAM mode = 0; + const char *errString = NULL; + + static const char *const subcommands[] = { + "broadcast", "delete", "get", "keys", "set", "type", "values", NULL + }; + enum SubCmdIdx { + BroadcastIdx, DeleteIdx, GetIdx, KeysIdx, SetIdx, TypeIdx, ValuesIdx + }; + static const char *const modes[] = { + "-32bit", "-64bit", NULL + }; + (void)dummy; + + if (objc < 2) { + wrongArgs: + Tcl_WrongNumArgs(interp, 1, objv, "?-32bit|-64bit? option ?arg ...?"); + return TCL_ERROR; + } + + if (Tcl_GetString(objv[n])[0] == '-') { + if (Tcl_GetIndexFromObj(interp, objv[n++], modes, "mode", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + switch (index) { + case 0: /* -32bit */ + mode |= KEY_WOW64_32KEY; + break; + case 1: /* -64bit */ + mode |= KEY_WOW64_64KEY; + break; + } + if (objc < 3) { + goto wrongArgs; + } + } + + if (Tcl_GetIndexFromObj(interp, objv[n++], subcommands, "option", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + + argc = (objc - n); + switch (index) { + case BroadcastIdx: /* broadcast */ + if (argc == 1 || argc == 3) { + int res = BroadcastValue(interp, argc, objv + n); + + if (res != TCL_BREAK) { + return res; + } + } + errString = "keyName ?-timeout milliseconds?"; + break; + case DeleteIdx: /* delete */ + if (argc == 1) { + return DeleteKey(interp, objv[n], mode); + } else if (argc == 2) { + return DeleteValue(interp, objv[n], objv[n+1], mode); + } + errString = "keyName ?valueName?"; + break; + case GetIdx: /* get */ + if (argc == 2) { + return GetValue(interp, objv[n], objv[n+1], mode); + } + errString = "keyName valueName"; + break; + case KeysIdx: /* keys */ + if (argc == 1) { + return GetKeyNames(interp, objv[n], NULL, mode); + } else if (argc == 2) { + return GetKeyNames(interp, objv[n], objv[n+1], mode); + } + errString = "keyName ?pattern?"; + break; + case SetIdx: /* set */ + if (argc == 1) { + HKEY key; + + /* + * Create the key and then close it immediately. + */ + + mode |= KEY_ALL_ACCESS; + if (OpenKey(interp, objv[n], mode, REG_CREATE, &key) != TCL_OK) { + return TCL_ERROR; + } + RegCloseKey(key); + return TCL_OK; + } else if (argc == 3) { + return SetValue(interp, objv[n], objv[n+1], objv[n+2], NULL, + mode); + } else if (argc == 4) { + return SetValue(interp, objv[n], objv[n+1], objv[n+2], objv[n+3], + mode); + } + errString = "keyName ?valueName data ?type??"; + break; + case TypeIdx: /* type */ + if (argc == 2) { + return GetType(interp, objv[n], objv[n+1], mode); + } + errString = "keyName valueName"; + break; + case ValuesIdx: /* values */ + if (argc == 1) { + return GetValueNames(interp, objv[n], NULL, mode); + } else if (argc == 2) { + return GetValueNames(interp, objv[n], objv[n+1], mode); + } + errString = "keyName ?pattern?"; + break; + } + Tcl_WrongNumArgs(interp, (mode ? 3 : 2), objv, errString); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * DeleteKey -- + * + * This function deletes a registry key. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +DeleteKey( + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Obj *keyNameObj, /* Name of key to delete. */ + REGSAM mode) /* Mode flags to pass. */ +{ + char *tail, *buffer, *hostName, *keyName; + const WCHAR *nativeTail; + HKEY rootKey, subkey; + DWORD result; + Tcl_DString buf; + REGSAM saveMode = mode; + Tcl_Size len; + + /* + * Find the parent of the key being deleted and open it. + */ + + keyName = Tcl_GetStringFromObj(keyNameObj, &len); + buffer = (char *)Tcl_Alloc(len + 1); + strcpy(buffer, keyName); + + if (ParseKeyName(interp, buffer, &hostName, &rootKey, + &keyName) != TCL_OK) { + Tcl_Free(buffer); + return TCL_ERROR; + } + + if (*keyName == '\0') { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("bad key: cannot delete root keys", -1)); + Tcl_SetErrorCode(interp, "WIN_REG", "DEL_ROOT_KEY", (char *)NULL); + Tcl_Free(buffer); + return TCL_ERROR; + } + + tail = strrchr(keyName, '\\'); + if (tail) { + *tail++ = '\0'; + } else { + tail = keyName; + keyName = NULL; + } + + mode |= KEY_ENUMERATE_SUB_KEYS | DELETE; + result = OpenSubKey(hostName, rootKey, keyName, mode, 0, &subkey); + if (result != ERROR_SUCCESS) { + Tcl_Free(buffer); + if (result == ERROR_FILE_NOT_FOUND) { + return TCL_OK; + } + Tcl_SetObjResult(interp, + Tcl_NewStringObj("unable to delete key: ", -1)); + AppendSystemError(interp, result); + return TCL_ERROR; + } + + /* + * Now we recursively delete the key and everything below it. + */ + + Tcl_DStringInit(&buf); + nativeTail = Tcl_UtfToWCharDString(tail, -1, &buf); + result = RecursiveDeleteKey(subkey, nativeTail, saveMode); + Tcl_DStringFree(&buf); + + if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("unable to delete key: ", -1)); + AppendSystemError(interp, result); + result = TCL_ERROR; + } else { + result = TCL_OK; + } + + RegCloseKey(subkey); + Tcl_Free(buffer); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * DeleteValue -- + * + * This function deletes a value from a registry key. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +DeleteValue( + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Obj *keyNameObj, /* Name of key. */ + Tcl_Obj *valueNameObj, /* Name of value to delete. */ + REGSAM mode) /* Mode flags to pass. */ +{ + HKEY key; + char *valueName; + DWORD result; + Tcl_DString ds; + Tcl_Size len; + + /* + * Attempt to open the key for deletion. + */ + + mode |= KEY_SET_VALUE; + if (OpenKey(interp, keyNameObj, mode, 0, &key) != TCL_OK) { + return TCL_ERROR; + } + + valueName = Tcl_GetStringFromObj(valueNameObj, &len); + Tcl_DStringInit(&ds); + Tcl_UtfToWCharDString(valueName, len, &ds); + result = RegDeleteValueW(key, (const WCHAR *)Tcl_DStringValue(&ds)); + Tcl_DStringFree(&ds); + if (result != ERROR_SUCCESS) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "unable to delete value \"%s\" from key \"%s\": ", + Tcl_GetString(valueNameObj), Tcl_GetString(keyNameObj))); + AppendSystemError(interp, result); + result = TCL_ERROR; + } else { + result = TCL_OK; + } + RegCloseKey(key); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * GetKeyNames -- + * + * This function enumerates the subkeys of a given key. If the optional + * pattern is supplied, then only keys that match the pattern will be + * returned. + * + * Results: + * Returns the list of subkeys in the result object of the interpreter, + * or an error message on failure. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetKeyNames( + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Obj *keyNameObj, /* Key to enumerate. */ + Tcl_Obj *patternObj, /* Optional match pattern. */ + REGSAM mode) /* Mode flags to pass. */ +{ + const char *pattern; /* Pattern being matched against subkeys */ + HKEY key; /* Handle to the key being examined */ + WCHAR buffer[MAX_KEY_LENGTH]; + /* Buffer to hold the subkey name */ + DWORD bufSize; /* Size of the buffer */ + DWORD index; /* Position of the current subkey */ + char *name; /* Subkey name */ + Tcl_Obj *resultPtr; /* List of subkeys being accumulated */ + int result = TCL_OK; /* Return value from this command */ + Tcl_DString ds; /* Buffer to translate subkey name to UTF-8 */ + + if (patternObj) { + pattern = Tcl_GetString(patternObj); + } else { + pattern = NULL; + } + + /* + * Attempt to open the key for enumeration. + */ + + mode |= KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS; + if (OpenKey(interp, keyNameObj, mode, 0, &key) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Enumerate the subkeys. + */ + + resultPtr = Tcl_NewObj(); + for (index = 0;; ++index) { + bufSize = MAX_KEY_LENGTH; + result = RegEnumKeyExW(key, index, buffer, &bufSize, + NULL, NULL, NULL, NULL); + if (result != ERROR_SUCCESS) { + if (result == ERROR_NO_MORE_ITEMS) { + result = TCL_OK; + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "unable to enumerate subkeys of \"%s\": ", + Tcl_GetString(keyNameObj))); + AppendSystemError(interp, result); + result = TCL_ERROR; + } + break; + } + Tcl_DStringInit(&ds); + name = Tcl_WCharToUtfDString(buffer, bufSize, &ds); + if (pattern && !Tcl_StringMatch(name, pattern)) { + Tcl_DStringFree(&ds); + continue; + } + result = Tcl_ListObjAppendElement(interp, resultPtr, + Tcl_NewStringObj(name, Tcl_DStringLength(&ds))); + Tcl_DStringFree(&ds); + if (result != TCL_OK) { + break; + } + } + if (result == TCL_OK) { + Tcl_SetObjResult(interp, resultPtr); + } else { + Tcl_DecrRefCount(resultPtr); /* BUGFIX: Don't leak on failure. */ + } + + RegCloseKey(key); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * GetType -- + * + * This function gets the type of a given registry value and places it in + * the interpreter result. + * + * Results: + * Returns a normal Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetType( + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Obj *keyNameObj, /* Name of key. */ + Tcl_Obj *valueNameObj, /* Name of value to get. */ + REGSAM mode) /* Mode flags to pass. */ +{ + HKEY key; + DWORD result, type; + Tcl_DString ds; + const char *valueName; + const WCHAR *nativeValue; + Tcl_Size len; + + /* + * Attempt to open the key for reading. + */ + + mode |= KEY_QUERY_VALUE; + if (OpenKey(interp, keyNameObj, mode, 0, &key) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Get the type of the value. + */ + + valueName = Tcl_GetStringFromObj(valueNameObj, &len); + Tcl_DStringInit(&ds); + nativeValue = Tcl_UtfToWCharDString(valueName, len, &ds); + result = RegQueryValueExW(key, nativeValue, NULL, &type, + NULL, NULL); + Tcl_DStringFree(&ds); + RegCloseKey(key); + + if (result != ERROR_SUCCESS) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "unable to get type of value \"%s\" from key \"%s\": ", + Tcl_GetString(valueNameObj), Tcl_GetString(keyNameObj))); + AppendSystemError(interp, result); + return TCL_ERROR; + } + + /* + * Set the type into the result. Watch out for unknown types. If we don't + * know about the type, just use the numeric value. + */ + + if (type > lastType) { + Tcl_SetObjResult(interp, Tcl_NewIntObj((int) type)); + } else { + Tcl_SetObjResult(interp, Tcl_NewStringObj(typeNames[type], -1)); + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GetValue -- + * + * This function gets the contents of a registry value and places a list + * containing the data and the type in the interpreter result. + * + * Results: + * Returns a normal Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetValue( + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Obj *keyNameObj, /* Name of key. */ + Tcl_Obj *valueNameObj, /* Name of value to get. */ + REGSAM mode) /* Mode flags to pass. */ +{ + HKEY key; + const char *valueName; + const WCHAR *nativeValue; + DWORD result, type, length; + Tcl_DString data, buf; + Tcl_Size len; + + /* + * Attempt to open the key for reading. + */ + + mode |= KEY_QUERY_VALUE; + if (OpenKey(interp, keyNameObj, mode, 0, &key) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Initialize a Dstring to maximum statically allocated size we could get + * one more byte by avoiding Tcl_DStringSetLength() and just setting + * length to TCL_DSTRING_STATIC_SIZE, but this should be safer if the + * implementation of Dstrings changes. + * + * This allows short values to be read from the registry in one call. + * Longer values need a second call with an expanded DString. + */ + + Tcl_DStringInit(&data); + Tcl_DStringSetLength(&data, TCL_DSTRING_STATIC_SIZE - 1); + length = TCL_DSTRING_STATIC_SIZE/sizeof(WCHAR) - 1; + + valueName = Tcl_GetStringFromObj(valueNameObj, &len); + Tcl_DStringInit(&buf); + nativeValue = Tcl_UtfToWCharDString(valueName, len, &buf); + + result = RegQueryValueExW(key, nativeValue, NULL, &type, + (BYTE *) Tcl_DStringValue(&data), &length); + while (result == ERROR_MORE_DATA) { + /* + * The Windows docs say that in this error case, we just need to + * expand our buffer and request more data. Required for + * HKEY_PERFORMANCE_DATA + */ + + length = (DWORD)(Tcl_DStringLength(&data) * (2 / sizeof(WCHAR))); + Tcl_DStringSetLength(&data, length * sizeof(WCHAR)); + result = RegQueryValueExW(key, nativeValue, + NULL, &type, (BYTE *) Tcl_DStringValue(&data), &length); + } + Tcl_DStringFree(&buf); + RegCloseKey(key); + if (result != ERROR_SUCCESS) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "unable to get value \"%s\" from key \"%s\": ", + Tcl_GetString(valueNameObj), Tcl_GetString(keyNameObj))); + AppendSystemError(interp, result); + Tcl_DStringFree(&data); + return TCL_ERROR; + } + + /* + * If the data is a 32-bit quantity, store it as an integer object. If it + * is a multi-string, store it as a list of strings. For null-terminated + * strings, append up the to first null. Otherwise, store it as a binary + * string. + */ + + if (type == REG_DWORD || type == REG_DWORD_BIG_ENDIAN) { + Tcl_SetObjResult(interp, Tcl_NewIntObj((int) ConvertDWORD(type, + *((DWORD *) Tcl_DStringValue(&data))))); + } else if (type == REG_MULTI_SZ) { + char *p = Tcl_DStringValue(&data); + char *end = Tcl_DStringValue(&data) + length; + Tcl_Obj *resultPtr = Tcl_NewObj(); + + /* + * Multistrings are stored as an array of null-terminated strings, + * terminated by two null characters. Also do a bounds check in case + * we get bogus data. + */ + + while ((p < end) && *((WCHAR *) p) != 0) { + WCHAR *wp = (WCHAR *) p; + + Tcl_DStringInit(&buf); + Tcl_WCharToUtfDString(wp, wcslen(wp), &buf); + Tcl_ListObjAppendElement(interp, resultPtr, + Tcl_NewStringObj(Tcl_DStringValue(&buf), + Tcl_DStringLength(&buf))); + + while (*wp++ != 0); /* empty loop body */ + p = (char *) wp; + Tcl_DStringFree(&buf); + } + Tcl_SetObjResult(interp, resultPtr); + } else if ((type == REG_SZ) || (type == REG_EXPAND_SZ)) { + WCHAR *wp = (WCHAR *) Tcl_DStringValue(&data); + Tcl_DStringInit(&buf); + Tcl_WCharToUtfDString((const WCHAR *)Tcl_DStringValue(&data), wcslen(wp), &buf); + Tcl_DStringResult(interp, &buf); + } else { + /* + * Save binary data as a byte array. + */ + + Tcl_SetObjResult(interp, Tcl_NewByteArrayObj( + (BYTE *) Tcl_DStringValue(&data), length)); + } + Tcl_DStringFree(&data); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * GetValueNames -- + * + * This function enumerates the values of the given key. If the + * optional pattern is supplied, then only value names that match the + * pattern will be returned. + * + * Results: + * Returns the list of value names in the result object of the + * interpreter, or an error message on failure. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetValueNames( + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Obj *keyNameObj, /* Key to enumerate. */ + Tcl_Obj *patternObj, /* Optional match pattern. */ + REGSAM mode) /* Mode flags to pass. */ +{ + HKEY key; + Tcl_Obj *resultPtr; + DWORD index, size, result; + Tcl_DString buffer, ds; + const char *pattern, *name; + + /* + * Attempt to open the key for enumeration. + */ + + mode |= KEY_QUERY_VALUE; + if (OpenKey(interp, keyNameObj, mode, 0, &key) != TCL_OK) { + return TCL_ERROR; + } + + resultPtr = Tcl_NewObj(); + Tcl_DStringInit(&buffer); + Tcl_DStringSetLength(&buffer, MAX_KEY_LENGTH * sizeof(WCHAR)); + index = 0; + result = TCL_OK; + + if (patternObj) { + pattern = Tcl_GetString(patternObj); + } else { + pattern = NULL; + } + + /* + * Enumerate the values under the given subkey until we get an error, + * indicating the end of the list. Note that we need to reset size after + * each iteration because RegEnumValue smashes the old value. + */ + + size = MAX_KEY_LENGTH; + while (RegEnumValueW(key,index, (WCHAR *)Tcl_DStringValue(&buffer), + &size, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString((const WCHAR *)Tcl_DStringValue(&buffer), size, &ds); + name = Tcl_DStringValue(&ds); + if (!pattern || Tcl_StringMatch(name, pattern)) { + result = Tcl_ListObjAppendElement(interp, resultPtr, + Tcl_NewStringObj(name, Tcl_DStringLength(&ds))); + if (result != TCL_OK) { + Tcl_DStringFree(&ds); + break; + } + } + Tcl_DStringFree(&ds); + + index++; + size = MAX_KEY_LENGTH; + } + Tcl_SetObjResult(interp, resultPtr); + Tcl_DStringFree(&buffer); + RegCloseKey(key); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * OpenKey -- + * + * This function opens the specified key. This function is a simple + * wrapper around ParseKeyName and OpenSubKey. + * + * Results: + * Returns the opened key in the keyPtr argument and a Tcl result code. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +OpenKey( + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Obj *keyNameObj, /* Key to open. */ + REGSAM mode, /* Access mode. */ + int flags, /* 0 or REG_CREATE. */ + HKEY *keyPtr) /* Returned HKEY. */ +{ + char *keyName, *buffer, *hostName; + HKEY rootKey; + DWORD result; + Tcl_Size len; + + keyName = Tcl_GetStringFromObj(keyNameObj, &len); + buffer = (char *)Tcl_Alloc(len + 1); + strcpy(buffer, keyName); + + result = ParseKeyName(interp, buffer, &hostName, &rootKey, &keyName); + if (result == TCL_OK) { + result = OpenSubKey(hostName, rootKey, keyName, mode, flags, keyPtr); + if (result != ERROR_SUCCESS) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("unable to open key: ", -1)); + AppendSystemError(interp, result); + result = TCL_ERROR; + } else { + result = TCL_OK; + } + } + + Tcl_Free(buffer); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * OpenSubKey -- + * + * Opens a given subkey of the given root key on the specified + * host. + * + * Results: + * Returns the opened key in the keyPtr and a Windows error code as the + * return value. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static DWORD +OpenSubKey( + char *hostName, /* Host to access, or NULL for local. */ + HKEY rootKey, /* Root registry key. */ + char *keyName, /* Subkey name. */ + REGSAM mode, /* Access mode. */ + int flags, /* 0 or REG_CREATE. */ + HKEY *keyPtr) /* Returned HKEY. */ +{ + DWORD result; + Tcl_DString buf; + + /* + * Attempt to open the root key on a remote host if necessary. + */ + + if (hostName) { + Tcl_DStringInit(&buf); + hostName = (char *) Tcl_UtfToWCharDString(hostName, -1, &buf); + result = RegConnectRegistryW((WCHAR *)hostName, rootKey, + &rootKey); + Tcl_DStringFree(&buf); + if (result != ERROR_SUCCESS) { + return result; + } + } + + /* + * Now open the specified key with the requested permissions. Note that + * this key must be closed by the caller. + */ + + if (keyName) { + Tcl_DStringInit(&buf); + keyName = (char *) Tcl_UtfToWCharDString(keyName, -1, &buf); + } + if (flags & REG_CREATE) { + DWORD create; + + result = RegCreateKeyExW(rootKey, (WCHAR *)keyName, 0, NULL, + REG_OPTION_NON_VOLATILE, mode, NULL, keyPtr, &create); + } else if (rootKey == HKEY_PERFORMANCE_DATA) { + /* + * Here we fudge it for this special root key. See MSDN for more info + * on HKEY_PERFORMANCE_DATA and the peculiarities surrounding it. + */ + + *keyPtr = HKEY_PERFORMANCE_DATA; + result = ERROR_SUCCESS; + } else { + result = RegOpenKeyExW(rootKey, (WCHAR *)keyName, 0, mode, + keyPtr); + } + if (keyName) { + Tcl_DStringFree(&buf); + } + + /* + * Be sure to close the root key since we are done with it now. + */ + + if (hostName) { + RegCloseKey(rootKey); + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * ParseKeyName -- + * + * Parses a key name into the host, root, and subkey parts. + * + * Results: + * The pointers to the start of the host and subkey names are returned in + * the hostNamePtr and keyNamePtr variables. The specified root HKEY is + * returned in rootKeyPtr. Returns a standard Tcl result. + * + * Side effects: + * Modifies the name string by inserting nulls. + * + *---------------------------------------------------------------------- + */ + +static int +ParseKeyName( + Tcl_Interp *interp, /* Current interpreter. */ + char *name, + char **hostNamePtr, + HKEY *rootKeyPtr, + char **keyNamePtr) +{ + char *rootName; + int result, index; + Tcl_Obj *rootObj; + + /* + * Split the key into host and root portions. + */ + + *hostNamePtr = *keyNamePtr = rootName = NULL; + if (name[0] == '\\') { + if (name[1] == '\\') { + *hostNamePtr = name; + for (rootName = name+2; *rootName != '\0'; rootName++) { + if (*rootName == '\\') { + *rootName++ = '\0'; + break; + } + } + } + } else { + rootName = name; + } + if (!rootName) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad key \"%s\": must start with a valid root", name)); + Tcl_SetErrorCode(interp, "WIN_REG", "NO_ROOT_KEY", (char *)NULL); + return TCL_ERROR; + } + + /* + * Split the root into root and subkey portions. + */ + + for (*keyNamePtr = rootName; **keyNamePtr != '\0'; (*keyNamePtr)++) { + if (**keyNamePtr == '\\') { + **keyNamePtr = '\0'; + (*keyNamePtr)++; + break; + } + } + + /* + * Look for a matching root name. + */ + + rootObj = Tcl_NewStringObj(rootName, -1); + result = Tcl_GetIndexFromObj(interp, rootObj, rootKeyNames, "root name", + TCL_EXACT, &index); + Tcl_DecrRefCount(rootObj); + if (result != TCL_OK) { + return TCL_ERROR; + } + *rootKeyPtr = rootKeys[index]; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * RecursiveDeleteKey -- + * + * This function recursively deletes all the keys below a starting key. + * Although Windows 95 does this automatically, we still need to do this + * for Windows NT. + * + * Results: + * Returns a Windows error code. + * + * Side effects: + * Deletes all of the keys and values below the given key. + * + *---------------------------------------------------------------------- + */ + +static DWORD +RecursiveDeleteKey( + HKEY startKey, /* Parent of key to be deleted. */ + const WCHAR *keyName, /* Name of key to be deleted in external + * encoding, not UTF. */ + REGSAM mode) /* Mode flags to pass. */ +{ + DWORD result, size; + Tcl_DString subkey; + HKEY hKey; + REGSAM saveMode = mode; + static int checkExProc = 0; + typedef LONG (* regDeleteKeyExProc) (HKEY, LPCWSTR, REGSAM, DWORD); + static regDeleteKeyExProc regDeleteKeyEx = (regDeleteKeyExProc) NULL; + /* Really RegDeleteKeyExW() but that's not + * available on all versions of Windows + * supported by Tcl. */ + + /* + * Do not allow NULL or empty key name. + */ + + if (!keyName || *keyName == '\0') { + return ERROR_BADKEY; + } + + mode |= KEY_ENUMERATE_SUB_KEYS | DELETE | KEY_QUERY_VALUE; + result = RegOpenKeyExW(startKey, keyName, 0, mode, &hKey); + if (result != ERROR_SUCCESS) { + return result; + } + + Tcl_DStringInit(&subkey); + Tcl_DStringSetLength(&subkey, MAX_KEY_LENGTH * sizeof(WCHAR)); + + mode = saveMode; + while (result == ERROR_SUCCESS) { + /* + * Always get index 0 because key deletion changes ordering. + */ + + size = MAX_KEY_LENGTH; + result = RegEnumKeyExW(hKey, 0, (WCHAR *)Tcl_DStringValue(&subkey), + &size, NULL, NULL, NULL, NULL); + if (result == ERROR_NO_MORE_ITEMS) { + /* + * RegDeleteKeyEx doesn't exist on non-64bit XP platforms, so we + * can't compile with it in. We need to check for it at runtime + * and use it if we find it. + */ + + if (mode && !checkExProc) { + HMODULE handle; + + checkExProc = 1; + handle = GetModuleHandleW(L"ADVAPI32"); + regDeleteKeyEx = (regDeleteKeyExProc) (void *) + GetProcAddress(handle, "RegDeleteKeyExW"); + } + if (mode && regDeleteKeyEx) { + result = regDeleteKeyEx(startKey, keyName, mode, 0); + } else { + result = RegDeleteKeyW(startKey, keyName); + } + break; + } else if (result == ERROR_SUCCESS) { + result = RecursiveDeleteKey(hKey, + (const WCHAR *) Tcl_DStringValue(&subkey), mode); + } + } + Tcl_DStringFree(&subkey); + RegCloseKey(hKey); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * SetValue -- + * + * This function sets the contents of a registry value. If the key or + * value does not exist, it will be created. If it does exist, then the + * data and type will be replaced. + * + * Results: + * Returns a normal Tcl result. + * + * Side effects: + * May create new keys or values. + * + *---------------------------------------------------------------------- + */ + +static int +SetValue( + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Obj *keyNameObj, /* Name of key. */ + Tcl_Obj *valueNameObj, /* Name of value to set. */ + Tcl_Obj *dataObj, /* Data to be written. */ + Tcl_Obj *typeObj, /* Type of data to be written. */ + REGSAM mode) /* Mode flags to pass. */ +{ + int type; + DWORD result; + HKEY key; + const char *valueName; + Tcl_DString nameBuf; + Tcl_Size len; + + if (typeObj == NULL) { + type = REG_SZ; + } else if (Tcl_GetIndexFromObj(interp, typeObj, typeNames, "type", + 0, (int *) &type) != TCL_OK) { + if (Tcl_GetIntFromObj(NULL, typeObj, (int *) &type) != TCL_OK) { + return TCL_ERROR; + } + Tcl_ResetResult(interp); + } + mode |= KEY_ALL_ACCESS; + if (OpenKey(interp, keyNameObj, mode, REG_CREATE, &key) != TCL_OK) { + return TCL_ERROR; + } + + valueName = Tcl_GetStringFromObj(valueNameObj, &len); + Tcl_DStringInit(&nameBuf); + valueName = (char *) Tcl_UtfToWCharDString(valueName, len, &nameBuf); + + if (type == REG_DWORD || type == REG_DWORD_BIG_ENDIAN) { + int value; + + if (Tcl_GetIntFromObj(interp, dataObj, &value) != TCL_OK) { + RegCloseKey(key); + Tcl_DStringFree(&nameBuf); + return TCL_ERROR; + } + + value = ConvertDWORD((DWORD) type, (DWORD) value); + result = RegSetValueExW(key, (WCHAR *) valueName, 0, + (DWORD) type, (BYTE *) &value, sizeof(DWORD)); + } else if (type == REG_MULTI_SZ) { + Tcl_DString data, buf; + Tcl_Size objc, i; + Tcl_Obj **objv; + + if (Tcl_ListObjGetElements(interp, dataObj, &objc, &objv) != TCL_OK) { + RegCloseKey(key); + Tcl_DStringFree(&nameBuf); + return TCL_ERROR; + } + + /* + * Append the elements as null terminated strings. Note that we must + * not assume the length of the string in case there are embedded + * nulls, which aren't allowed in REG_MULTI_SZ values. + */ + + Tcl_DStringInit(&data); + for (i = 0; i < objc; i++) { + const char *bytes = Tcl_GetStringFromObj(objv[i], &len); + + Tcl_DStringAppend(&data, bytes, len); + + /* + * Add a null character to separate this value from the next. + */ + + Tcl_DStringAppend(&data, "", 1); /* NUL-terminated string */ + } + + Tcl_DStringInit(&buf); + Tcl_UtfToWCharDString(Tcl_DStringValue(&data), Tcl_DStringLength(&data)+1, + &buf); + result = RegSetValueExW(key, (WCHAR *) valueName, 0, + (DWORD) type, (BYTE *) Tcl_DStringValue(&buf), + (DWORD) Tcl_DStringLength(&buf)); + Tcl_DStringFree(&data); + Tcl_DStringFree(&buf); + } else if (type == REG_SZ || type == REG_EXPAND_SZ) { + Tcl_DString buf; + const char *data = Tcl_GetStringFromObj(dataObj, &len); + + Tcl_DStringInit(&buf); + data = (char *) Tcl_UtfToWCharDString(data, len, &buf); + + /* + * Include the null in the length, padding if needed for WCHAR. + */ + + Tcl_DStringSetLength(&buf, Tcl_DStringLength(&buf)+1); + + result = RegSetValueExW(key, (WCHAR *) valueName, 0, + (DWORD) type, (BYTE *) data, (DWORD) Tcl_DStringLength(&buf) + 1); + Tcl_DStringFree(&buf); + } else { + BYTE *data; + Tcl_Size bytelength; + + /* + * Store binary data in the registry. + */ + + data = (BYTE *) Tcl_GetByteArrayFromObj(dataObj, &bytelength); + result = RegSetValueExW(key, (WCHAR *) valueName, 0, + (DWORD) type, data, (DWORD) bytelength); + } + + Tcl_DStringFree(&nameBuf); + RegCloseKey(key); + + if (result != ERROR_SUCCESS) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("unable to set value: ", -1)); + AppendSystemError(interp, result); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * BroadcastValue -- + * + * This function broadcasts a WM_SETTINGCHANGE message to indicate to + * other programs that we have changed the contents of a registry value. + * + * Results: + * Returns a normal Tcl result. + * + * Side effects: + * Will cause other programs to reload their system settings. + * + *---------------------------------------------------------------------- + */ + +static int +BroadcastValue( + Tcl_Interp *interp, /* Current interpreter. */ + Tcl_Size objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument values. */ +{ + LRESULT result; + DWORD_PTR sendResult; + int timeout = 3000; + Tcl_Size len; + const char *str; + Tcl_Obj *objPtr; + WCHAR *wstr; + Tcl_DString ds; + + if (objc == 3) { + str = Tcl_GetStringFromObj(objv[1], &len); + if ((len < 2) || (*str != '-') || strncmp(str, "-timeout", len)) { + return TCL_BREAK; + } + if (Tcl_GetIntFromObj(interp, objv[2], &timeout) != TCL_OK) { + return TCL_ERROR; + } + } + + str = Tcl_GetStringFromObj(objv[0], &len); + Tcl_DStringInit(&ds); + wstr = Tcl_UtfToWCharDString(str, len, &ds); + if (Tcl_DStringLength(&ds) == 0) { + wstr = NULL; + } + + /* + * Use the ignore the result. + */ + + result = SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, + (WPARAM) 0, (LPARAM) wstr, SMTO_ABORTIFHUNG, (UINT) timeout, &sendResult); + Tcl_DStringFree(&ds); + + objPtr = Tcl_NewObj(); + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewWideIntObj((Tcl_WideInt) result)); + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewWideIntObj((Tcl_WideInt) sendResult)); + Tcl_SetObjResult(interp, objPtr); + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * AppendSystemError -- + * + * Formats a Windows system error message and places it into + * the interpreter result. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +AppendSystemError( + Tcl_Interp *interp, /* Current interpreter. */ + DWORD error) /* Result code from error. */ +{ + Tcl_Size length; + WCHAR *tMsgPtr, **tMsgPtrPtr = &tMsgPtr; + const char *msg; + char id[TCL_INTEGER_SPACE], msgBuf[24 + TCL_INTEGER_SPACE]; + Tcl_DString ds; + Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); + + if (Tcl_IsShared(resultPtr)) { + resultPtr = Tcl_DuplicateObj(resultPtr); + } + length = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (WCHAR *) tMsgPtrPtr, + 0, NULL); + if (length == 0) { + snprintf(msgBuf, sizeof(msgBuf), "unknown error: %ld", error); + msg = msgBuf; + } else { + char *msgPtr; + + Tcl_DStringInit(&ds); + Tcl_WCharToUtfDString(tMsgPtr, wcslen(tMsgPtr), &ds); + LocalFree(tMsgPtr); + + msgPtr = Tcl_DStringValue(&ds); + length = Tcl_DStringLength(&ds); + + /* + * Trim the trailing CR/LF from the system message. + */ + + if (msgPtr[length-1] == '\n') { + --length; + } + if (msgPtr[length-1] == '\r') { + --length; + } + msgPtr[length] = 0; + msg = msgPtr; + } + + snprintf(id, sizeof(id), "%ld", error); + Tcl_SetErrorCode(interp, "WINDOWS", id, msg, (char *)NULL); + Tcl_AppendToObj(resultPtr, msg, length); + Tcl_SetObjResult(interp, resultPtr); + + if (length != 0) { + Tcl_DStringFree(&ds); + } +} + +/* + *---------------------------------------------------------------------- + * + * ConvertDWORD -- + * + * Determines whether a DWORD needs to be byte swapped, and + * returns the appropriately swapped value. + * + * Results: + * Returns a converted DWORD. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static DWORD +ConvertDWORD( + DWORD type, /* Either REG_DWORD or REG_DWORD_BIG_ENDIAN */ + DWORD value) /* The value to be converted. */ +{ + const DWORD order = 1; + DWORD localType; + + /* + * Check to see if the low bit is in the first byte. + */ + + localType = (*((const char *) &order) == 1) + ? REG_DWORD : REG_DWORD_BIG_ENDIAN; + return (type != localType) ? (DWORD) SWAPLONG(value) : value; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclWinSerial.c b/vendor/tcl/win/tclWinSerial.c new file mode 100644 index 00000000..3673393b --- /dev/null +++ b/vendor/tcl/win/tclWinSerial.c @@ -0,0 +1,2317 @@ +/* + * tclWinSerial.c -- + * + * This file implements the Windows-specific serial port functions, and + * the "serial" channel driver. + * + * Copyright © 1999 Scriptics Corp. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * Serial functionality implemented by Rolf.Schroedter@dlr.de + */ + +#include "tclWinInt.h" +#if defined (__clang__) && (__clang_major__ > 20) +#pragma clang diagnostic ignored "-Wc++-keyword" +#endif + + +/* + * The following variable is used to tell whether this module has been + * initialized. + */ + +static int initialized = 0; + +/* + * The serialMutex locks around access to the initialized variable, and it is + * used to protect background threads from being terminated while they are + * using APIs that hold locks. + */ + +TCL_DECLARE_MUTEX(serialMutex) + +/* + * Bit masks used in the flags field of the SerialInfo structure below. + */ +enum SerialFlags { + SERIAL_PENDING = 1 << 0, /* Message is pending in the queue. */ + SERIAL_ASYNC = 1 << 1, /* Channel is non-blocking. */ + + /* + * Bit masks used in the sharedFlags field of the SerialInfo structure + * below. + */ + + SERIAL_EOF = 1 << 2, /* Serial has reached EOF. */ + SERIAL_ERROR = 1 << 4, + + /* + * Bit masks used for noting whether to drain or discard output on close. + * They are disjoint from each other; at most one may be set at a time. + */ + + SERIAL_CLOSE_DRAIN = 1<<6, /* Drain all output on close. */ + SERIAL_CLOSE_DISCARD = 1<<7,/* Discard all output on close. */ + SERIAL_CLOSE_MASK = 3<<6 /* Both two bits above. */ +}; + +/* + * Default time to block between checking status on the serial port. + */ + +#define SERIAL_DEFAULT_BLOCKTIME 10 /* 10 msec */ + +/* + * Win32 read/write error masks for values returned by ClearCommError() + */ +enum TclWinCommErrorMasks { + SERIAL_READ_ERRORS = /* Errors in the reader side. */ + (CE_RXOVER | CE_OVERRUN | CE_RXPARITY | CE_FRAME | CE_BREAK), + SERIAL_WRITE_ERRORS = /* Errors in the writer side. */ + (CE_TXFULL | CE_PTO) +}; + +/* + * This structure describes per-instance data for a serial based channel. + */ + +typedef struct SerialInfo { + HANDLE handle; + struct SerialInfo *nextPtr; /* Pointer to next registered serial. */ + Tcl_Channel channel; /* Pointer to channel structure. */ + int validMask; /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, or TCL_EXCEPTION: indicates + * which operations are valid on the file. */ + int watchMask; /* OR'ed combination of TCL_READABLE, + * TCL_WRITABLE, or TCL_EXCEPTION: indicates + * which events should be reported. */ + int flags; /* State flags, see above for a list. */ + int readable; /* Flag that the channel is readable. */ + int writable; /* Flag that the channel is writable. */ + int blockTime; /* Maximum blocktime in msec. */ + unsigned long long lastEventTime; + /* Time in milliseconds since last readable + * event. */ + /* Next readable event only after blockTime */ + DWORD error; /* pending error code returned by + * ClearCommError() */ + DWORD lastError; /* last error code, can be fetched with + * fconfigure chan -lasterror */ + DWORD sysBufRead; /* Win32 system buffer size for read ops, + * default=4096 */ + DWORD sysBufWrite; /* Win32 system buffer size for write ops, + * default=4096 */ + + Tcl_ThreadId threadId; /* Thread to which events should be reported. + * This value is used by the reader/writer + * threads. */ + OVERLAPPED osRead; /* OVERLAPPED structure for read operations. */ + OVERLAPPED osWrite; /* OVERLAPPED structure for write operations */ + TclPipeThreadInfo *writeTI; /* Thread info structure of writer worker. */ + HANDLE writeThread; /* Handle to writer thread. */ + CRITICAL_SECTION csWrite; /* Writer thread synchronisation. */ + HANDLE evWritable; /* Manual-reset event to signal when the + * writer thread has finished waiting for the + * current buffer to be written. */ + DWORD writeError; /* An error caused by the last background + * write. Set to 0 if no error has been + * detected. This word is shared with the + * writer thread so access must be + * synchronized with the evWritable object. */ + char *writeBuf; /* Current background output buffer. Access is + * synchronized with the evWritable object. */ + int writeBufLen; /* Size of write buffer. Access is + * synchronized with the evWritable object. */ + int toWrite; /* Current amount to be written. Access is + * synchronized with the evWritable object. */ + int writeQueue; /* Number of bytes pending in output queue. + * Offset to DCB.cbInQue. Used to query + * [fconfigure -queue] */ +} SerialInfo; + +typedef struct { + /* + * The following pointer refers to the head of the list of serials that + * are being watched for file events. + */ + + SerialInfo *firstSerialPtr; +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +/* + * The following structure is what is added to the Tcl event queue when serial + * events are generated. + */ + +typedef struct { + Tcl_Event header; /* Information that is standard for all + * events. */ + SerialInfo *infoPtr; /* Pointer to serial info structure. Note that + * we still have to verify that the serial + * exists before dereferencing this + * pointer. */ +} SerialEvent; + +/* + * We don't use timeouts. + */ + +static COMMTIMEOUTS no_timeout = { + 0, /* ReadIntervalTimeout */ + 0, /* ReadTotalTimeoutMultiplier */ + 0, /* ReadTotalTimeoutConstant */ + 0, /* WriteTotalTimeoutMultiplier */ + 0, /* WriteTotalTimeoutConstant */ +}; + +/* + * Declarations for functions used only in this file. + */ + +static int SerialBlockProc(void *instanceData, int mode); +static void SerialCheckProc(void *clientData, int flags); +static int SerialCloseProc(void *instanceData, + Tcl_Interp *interp, int flags); +static int SerialEventProc(Tcl_Event *evPtr, int flags); +static void SerialExitHandler(void *clientData); +static int SerialGetHandleProc(void *instanceData, + int direction, void **handlePtr); +static ThreadSpecificData *SerialInit(void); +static int SerialInputProc(void *instanceData, char *buf, + int toRead, int *errorCode); +static int SerialOutputProc(void *instanceData, + const char *buf, int toWrite, int *errorCode); +static void SerialSetupProc(void *clientData, int flags); +static void SerialWatchProc(void *instanceData, int mask); +static void ProcExitHandler(void *clientData); +static int SerialGetOptionProc(void *instanceData, + Tcl_Interp *interp, const char *optionName, + Tcl_DString *dsPtr); +static int SerialSetOptionProc(void *instanceData, + Tcl_Interp *interp, const char *optionName, + const char *value); +static DWORD WINAPI SerialWriterThread(LPVOID arg); +static void SerialThreadActionProc(void *instanceData, + int action); +static int SerialBlockingRead(SerialInfo *infoPtr, LPVOID buf, + DWORD bufSize, LPDWORD lpRead, LPOVERLAPPED osPtr); +static int SerialBlockingWrite(SerialInfo *infoPtr, LPVOID buf, + DWORD bufSize, LPDWORD lpWritten, + LPOVERLAPPED osPtr); + +/* + * This structure describes the channel type structure for command serial + * based IO. + */ + +static const Tcl_ChannelType serialChannelType = { + "serial", + TCL_CHANNEL_VERSION_5, + NULL, /* Deprecated. */ + SerialInputProc, + SerialOutputProc, + NULL, /* Deprecated. */ + SerialSetOptionProc, + SerialGetOptionProc, + SerialWatchProc, + SerialGetHandleProc, + SerialCloseProc, + SerialBlockProc, + NULL, /* Flush proc. */ + NULL, /* Bubbled event handler proc. */ + NULL, /* Seek proc. */ + SerialThreadActionProc, + NULL /* Truncate proc. */ +}; + +/* + *---------------------------------------------------------------------- + * + * SerialInit -- + * + * This function initializes the static variables for this file. + * + * Results: + * None. + * + * Side effects: + * Creates a new event source. + * + *---------------------------------------------------------------------- + */ + +static ThreadSpecificData * +SerialInit(void) +{ + ThreadSpecificData *tsdPtr; + + /* + * Check the initialized flag first, then check it again in the mutex. + * This is a speed enhancement. + */ + + if (!initialized) { + Tcl_MutexLock(&serialMutex); + if (!initialized) { + initialized = 1; + Tcl_CreateExitHandler(ProcExitHandler, NULL); + } + Tcl_MutexUnlock(&serialMutex); + } + + tsdPtr = (ThreadSpecificData *) TclThreadDataKeyGet(&dataKey); + if (tsdPtr == NULL) { + tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->firstSerialPtr = NULL; + Tcl_CreateEventSource(SerialSetupProc, SerialCheckProc, NULL); + Tcl_CreateThreadExitHandler(SerialExitHandler, NULL); + } + return tsdPtr; +} + +/* + *---------------------------------------------------------------------- + * + * SerialExitHandler -- + * + * This function is called to cleanup the serial module before Tcl is + * unloaded. + * + * Results: + * None. + * + * Side effects: + * Removes the serial event source. + * + *---------------------------------------------------------------------- + */ + +static void +SerialExitHandler( + TCL_UNUSED(void *)) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + SerialInfo *infoPtr; + + /* + * Clear all eventually pending output. Otherwise Tcl's exit could totally + * block, because it performs a blocking flush on all open channels. Note + * that serial write operations may be blocked due to handshake. + */ + + for (infoPtr = tsdPtr->firstSerialPtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + PurgeComm(infoPtr->handle, + PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR); + } + Tcl_DeleteEventSource(SerialSetupProc, SerialCheckProc, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * ProcExitHandler -- + * + * This function is called to cleanup the process list before Tcl is + * unloaded. + * + * Results: + * None. + * + * Side effects: + * Resets the process list. + * + *---------------------------------------------------------------------- + */ + +static void +ProcExitHandler( + TCL_UNUSED(void *)) +{ + Tcl_MutexLock(&serialMutex); + initialized = 0; + Tcl_MutexUnlock(&serialMutex); +} + +/* + *---------------------------------------------------------------------- + * + * SerialBlockTime -- + * + * Wrapper to set Tcl's block time in msec. + * + * Results: + * None. + * + * Side effects: + * Updates the maximum blocking time. + * + *---------------------------------------------------------------------- + */ + +static void +SerialBlockTime( + int msec) /* milli-seconds */ +{ + Tcl_Time blockTime; + + blockTime.sec = msec / 1000; + blockTime.usec = (msec % 1000) * 1000; + Tcl_SetMaxBlockTime(&blockTime); +} + +/* + *---------------------------------------------------------------------- + * + * SerialGetMilliseconds -- + * + * Get current time in milliseconds,ignoring integer overruns. + * + * Results: + * The current time. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static unsigned long long +SerialGetMilliseconds(void) +{ + Tcl_Time time; + + Tcl_GetTime(&time); + + return (unsigned long long)time.sec * 1000 + + (unsigned long)time.usec / 1000; +} + +/* + *---------------------------------------------------------------------- + * + * SerialSetupProc -- + * + * This procedure is invoked before Tcl_DoOneEvent blocks waiting for an + * event. + * + * Results: + * None. + * + * Side effects: + * Adjusts the block time if needed. + * + *---------------------------------------------------------------------- + */ + +#ifdef __cplusplus +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +void +SerialSetupProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + SerialInfo *infoPtr; + int block = 1; + int msec = INT_MAX; /* min. found block time */ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + /* + * Look to see if any events handlers installed. If they are, do not + * block. + */ + + for (infoPtr=tsdPtr->firstSerialPtr ; infoPtr!=NULL ; + infoPtr=infoPtr->nextPtr) { + if (infoPtr->watchMask & TCL_WRITABLE) { + if (WaitForSingleObject(infoPtr->evWritable, 0) != WAIT_TIMEOUT) { + block = 0; + msec = min(msec, infoPtr->blockTime); + } + } + if (infoPtr->watchMask & TCL_READABLE) { + block = 0; + msec = min(msec, infoPtr->blockTime); + } + } + + if (!block) { + SerialBlockTime(msec); + } +} + +/* + *---------------------------------------------------------------------- + * + * SerialCheckProc -- + * + * This procedure is called by Tcl_DoOneEvent to check the serial event + * source for events. + * + * Results: + * None. + * + * Side effects: + * May queue an event. + * + *---------------------------------------------------------------------- + */ + +static void +SerialCheckProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + SerialInfo *infoPtr; + SerialEvent *evPtr; + int needEvent; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + COMSTAT cStat; + unsigned long long time; + + if (!(flags & TCL_FILE_EVENTS)) { + return; + } + + /* + * Queue events for any ready serials that don't already have events + * queued. + */ + + for (infoPtr=tsdPtr->firstSerialPtr ; infoPtr!=NULL ; + infoPtr=infoPtr->nextPtr) { + if (infoPtr->flags & SERIAL_PENDING) { + continue; + } + + needEvent = 0; + + /* + * If WRITABLE watch mask is set look for infoPtr->evWritable object. + */ + + if (infoPtr->watchMask & TCL_WRITABLE && + WaitForSingleObject(infoPtr->evWritable, 0) != WAIT_TIMEOUT) { + infoPtr->writable = 1; + needEvent = 1; + } + + /* + * If READABLE watch mask is set call ClearCommError to poll cbInQue. + * Window errors are ignored here. + */ + + if (infoPtr->watchMask & TCL_READABLE) { + if (ClearCommError(infoPtr->handle, &infoPtr->error, &cStat)) { + /* + * Look for characters already pending in windows queue. If + * they are, poll. + */ + + if (infoPtr->watchMask & TCL_READABLE) { + /* + * Force fileevent after serial read error. + */ + + if ((cStat.cbInQue > 0) || + (infoPtr->error & SERIAL_READ_ERRORS)) { + infoPtr->readable = 1; + time = SerialGetMilliseconds(); + if ((time - infoPtr->lastEventTime) + >= (unsigned long long) infoPtr->blockTime) { + needEvent = 1; + infoPtr->lastEventTime = time; + } + } + } + } + } + + /* + * Queue an event if the serial is signaled for reading or writing. + */ + + if (needEvent) { + infoPtr->flags |= SERIAL_PENDING; + evPtr = (SerialEvent *)Tcl_Alloc(sizeof(SerialEvent)); + evPtr->header.proc = SerialEventProc; + evPtr->infoPtr = infoPtr; + Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * SerialBlockProc -- + * + * Set blocking or non-blocking mode on channel. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or non-blocking mode. + * + *---------------------------------------------------------------------- + */ + +static int +SerialBlockProc( + void *instanceData, /* Instance data for channel. */ + int mode) /* TCL_MODE_BLOCKING or + * TCL_MODE_NONBLOCKING. */ +{ + int errorCode = 0; + SerialInfo *infoPtr = (SerialInfo *) instanceData; + + /* + * Only serial READ can be switched between blocking & nonblocking using + * COMMTIMEOUTS. Serial write emulates blocking & nonblocking by the + * SerialWriterThread. + */ + + if (mode == TCL_MODE_NONBLOCKING) { + infoPtr->flags |= SERIAL_ASYNC; + } else { + infoPtr->flags &= ~(SERIAL_ASYNC); + } + return errorCode; +} + +/* + *---------------------------------------------------------------------- + * + * SerialCloseProc -- + * + * Closes a serial based IO channel. + * + * Results: + * 0 on success, errno otherwise. + * + * Side effects: + * Closes the physical channel. + * + *---------------------------------------------------------------------- + */ + +static int +SerialCloseProc( + void *instanceData, /* Pointer to SerialInfo structure. */ + TCL_UNUSED(Tcl_Interp *), + int flags) +{ + SerialInfo *serialPtr = (SerialInfo *) instanceData; + int errorCode = 0, result = 0; + SerialInfo *infoPtr, **nextPtrPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { + return EINVAL; + } + + if (serialPtr->validMask & TCL_READABLE) { + PurgeComm(serialPtr->handle, PURGE_RXABORT | PURGE_RXCLEAR); + CloseHandle(serialPtr->osRead.hEvent); + } + serialPtr->validMask &= ~TCL_READABLE; + + if (serialPtr->writeThread) { + TclPipeThreadStop(&serialPtr->writeTI, serialPtr->writeThread); + + CloseHandle(serialPtr->osWrite.hEvent); + CloseHandle(serialPtr->evWritable); + CloseHandle(serialPtr->writeThread); + serialPtr->writeThread = NULL; + + PurgeComm(serialPtr->handle, PURGE_TXABORT | PURGE_TXCLEAR); + } + serialPtr->validMask &= ~TCL_WRITABLE; + + DeleteCriticalSection(&serialPtr->csWrite); + + /* + * Don't close the Win32 handle if the handle is a standard channel during + * the thread exit process. Otherwise, one thread may kill the stdio of + * another. + */ + + if (!TclInThreadExit() + || ((GetStdHandle(STD_INPUT_HANDLE) != serialPtr->handle) + && (GetStdHandle(STD_OUTPUT_HANDLE) != serialPtr->handle) + && (GetStdHandle(STD_ERROR_HANDLE) != serialPtr->handle))) { + if (CloseHandle(serialPtr->handle) == FALSE) { + Tcl_WinConvertError(GetLastError()); + errorCode = errno; + } + } + + serialPtr->watchMask &= serialPtr->validMask; + + /* + * Remove the file from the list of watched files. + */ + + for (nextPtrPtr=&(tsdPtr->firstSerialPtr), infoPtr=*nextPtrPtr; + infoPtr!=NULL; + nextPtrPtr=&infoPtr->nextPtr, infoPtr=*nextPtrPtr) { + if (infoPtr == (SerialInfo *)serialPtr) { + *nextPtrPtr = infoPtr->nextPtr; + break; + } + } + + /* + * Wrap the error file into a channel and give it to the cleanup routine. + */ + + if (serialPtr->writeBuf != NULL) { + Tcl_Free(serialPtr->writeBuf); + serialPtr->writeBuf = NULL; + } + Tcl_Free(serialPtr); + + if (errorCode == 0) { + return result; + } + return errorCode; +} + +/* + *---------------------------------------------------------------------- + * + * SerialBlockingRead -- + * + * Perform a blocking read into the buffer given. Returns count of how + * many bytes were actually read, and an error indication. + * + * Results: + * A count of how many bytes were read is returned and an error + * indication is returned. + * + * Side effects: + * Reads input from the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +SerialBlockingRead( + SerialInfo *infoPtr, /* Serial info structure */ + LPVOID buf, /* The input buffer pointer */ + DWORD bufSize, /* The number of bytes to read */ + LPDWORD lpRead, /* Returns number of bytes read */ + LPOVERLAPPED osPtr) /* OVERLAPPED structure */ +{ + /* + * Perform overlapped blocking read. + * 1. Reset the overlapped event + * 2. Start overlapped read operation + * 3. Wait for completion + */ + + /* + * Set Offset to ZERO, otherwise NT4.0 may report an error. + */ + + osPtr->Offset = osPtr->OffsetHigh = 0; + ResetEvent(osPtr->hEvent); + if (!ReadFile(infoPtr->handle, buf, bufSize, lpRead, osPtr)) { + if (GetLastError() != ERROR_IO_PENDING) { + /* + * ReadFile failed, but it isn't delayed. Report error. + */ + + return FALSE; + } else { + /* + * Read is pending, wait for completion, timeout? + */ + + if (!GetOverlappedResult(infoPtr->handle, osPtr, lpRead, TRUE)) { + return FALSE; + } + } + } else { + /* + * ReadFile completed immediately. + */ + } + return TRUE; +} + +/* + *---------------------------------------------------------------------- + * + * SerialBlockingWrite -- + * + * Perform a blocking write from the buffer given. Returns count of how + * many bytes were actually written, and an error indication. + * + * Results: + * A count of how many bytes were written is returned and an error + * indication is returned. + * + * Side effects: + * Writes output to the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +SerialBlockingWrite( + SerialInfo *infoPtr, /* Serial info structure */ + LPVOID buf, /* The output buffer pointer */ + DWORD bufSize, /* The number of bytes to write */ + LPDWORD lpWritten, /* Returns number of bytes written */ + LPOVERLAPPED osPtr) /* OVERLAPPED structure */ +{ + int result; + + /* + * Perform overlapped blocking write. + * 1. Reset the overlapped event + * 2. Remove these bytes from the output queue counter + * 3. Start overlapped write operation + * 3. Remove these bytes from the output queue counter + * 4. Wait for completion + * 5. Adjust the output queue counter + */ + + ResetEvent(osPtr->hEvent); + + EnterCriticalSection(&infoPtr->csWrite); + infoPtr->writeQueue -= bufSize; + + /* + * Set Offset to ZERO, otherwise NT4.0 may report an error + */ + + osPtr->Offset = osPtr->OffsetHigh = 0; + result = WriteFile(infoPtr->handle, buf, bufSize, lpWritten, osPtr); + LeaveCriticalSection(&infoPtr->csWrite); + + if (result == FALSE) { + DWORD err = GetLastError(); + + switch (err) { + case ERROR_IO_PENDING: + /* + * Write is pending, wait for completion. + */ + + if (!GetOverlappedResult(infoPtr->handle, osPtr, lpWritten, + TRUE)) { + return FALSE; + } + break; + case ERROR_COUNTER_TIMEOUT: + /* + * Write timeout handled in SerialOutputProc. + */ + + break; + default: + /* + * WriteFile failed, but it isn't delayed. Report error. + */ + + return FALSE; + } + } else { + /* + * WriteFile completed immediately. + */ + } + + EnterCriticalSection(&infoPtr->csWrite); + infoPtr->writeQueue += (*lpWritten - bufSize); + LeaveCriticalSection(&infoPtr->csWrite); + + return TRUE; +} + +/* + *---------------------------------------------------------------------- + * + * SerialInputProc -- + * + * Reads input from the IO channel into the buffer given. Returns count + * of how many bytes were actually read, and an error indication. + * + * Results: + * A count of how many bytes were read is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Reads input from the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +SerialInputProc( + void *instanceData, /* Serial state. */ + char *buf, /* Where to store data read. */ + int bufSize, /* How much space is available in the + * buffer? */ + int *errorCode) /* Where to store error code. */ +{ + SerialInfo *infoPtr = (SerialInfo *) instanceData; + DWORD bytesRead = 0; + COMSTAT cStat; + + *errorCode = 0; + + /* + * Check if there is a CommError pending from SerialCheckProc + */ + + if (infoPtr->error & SERIAL_READ_ERRORS) { + goto commError; + } + + /* + * Look for characters already pending in windows queue. This is the + * mainly restored good old code from Tcl8.0 + */ + + if (ClearCommError(infoPtr->handle, &infoPtr->error, &cStat)) { + /* + * Check for errors here, but not in the evSetup/Check procedures. + */ + + if (infoPtr->error & SERIAL_READ_ERRORS) { + goto commError; + } + if (infoPtr->flags & SERIAL_ASYNC) { + /* + * NON_BLOCKING mode: Avoid blocking by reading more bytes than + * available in input buffer. + */ + + if (cStat.cbInQue > 0) { + if ((DWORD) bufSize > cStat.cbInQue) { + bufSize = cStat.cbInQue; + } + } else { + errno = *errorCode = EWOULDBLOCK; + return -1; + } + } else { + /* + * BLOCKING mode: Tcl tries to read a full buffer of 4 kBytes here. + */ + + if (cStat.cbInQue > 0) { + if ((DWORD) bufSize > cStat.cbInQue) { + bufSize = cStat.cbInQue; + } + } else { + bufSize = 1; + } + } + } + + if (bufSize == 0) { + return 0; + } + + /* + * Perform blocking read. Doesn't block in non-blocking mode, because we + * checked the number of available bytes. + */ + + if (SerialBlockingRead(infoPtr, (LPVOID) buf, (DWORD) bufSize, &bytesRead, + &infoPtr->osRead) == FALSE) { + Tcl_WinConvertError(GetLastError()); + *errorCode = errno; + return -1; + } + return bytesRead; + + commError: + infoPtr->lastError = infoPtr->error; + /* save last error code */ + infoPtr->error = 0; /* reset error code */ + *errorCode = EIO; /* to return read-error only once */ + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * SerialOutputProc -- + * + * Writes the given output on the IO channel. Returns count of how many + * characters were actually written, and an error indication. + * + * Results: + * A count of how many characters were written is returned and an error + * indication is returned in an output argument. + * + * Side effects: + * Writes output on the actual channel. + * + *---------------------------------------------------------------------- + */ + +static int +SerialOutputProc( + void *instanceData, /* Serial state. */ + const char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCode) /* Where to store error code. */ +{ + SerialInfo *infoPtr = (SerialInfo *) instanceData; + DWORD bytesWritten, timeout; + + *errorCode = 0; + + /* + * At EXIT Tcl tries to flush all open channels in blocking mode. We avoid + * blocking output after ExitProc or CloseHandler(chan) has been called by + * checking the corresponding variables. + */ + + if (!initialized || TclInExit()) { + return toWrite; + } + + /* + * Check if there is a CommError pending from SerialCheckProc + */ + + if (infoPtr->error & SERIAL_WRITE_ERRORS) { + infoPtr->lastError = infoPtr->error; + /* save last error code */ + infoPtr->error = 0; /* reset error code */ + errno = EIO; + goto error; + } + + timeout = (infoPtr->flags & SERIAL_ASYNC) ? 0 : INFINITE; + if (WaitForSingleObject(infoPtr->evWritable, timeout) == WAIT_TIMEOUT) { + /* + * The writer thread is blocked waiting for a write to complete and + * the channel is in non-blocking mode. + */ + + errno = EWOULDBLOCK; + goto error1; + } + + /* + * Check for a background error on the last write. + */ + + if (infoPtr->writeError) { + Tcl_WinConvertError(infoPtr->writeError); + infoPtr->writeError = 0; + goto error1; + } + + /* + * Remember the number of bytes in output queue + */ + + EnterCriticalSection(&infoPtr->csWrite); + infoPtr->writeQueue += toWrite; + LeaveCriticalSection(&infoPtr->csWrite); + + if (infoPtr->flags & SERIAL_ASYNC) { + /* + * The serial is non-blocking, so copy the data into the output buffer + * and restart the writer thread. + */ + + if (toWrite > infoPtr->writeBufLen) { + /* + * Reallocate the buffer to be large enough to hold the data. + */ + + if (infoPtr->writeBuf) { + Tcl_Free(infoPtr->writeBuf); + } + infoPtr->writeBufLen = toWrite; + infoPtr->writeBuf = (char *)Tcl_Alloc(toWrite); + } + memcpy(infoPtr->writeBuf, buf, toWrite); + infoPtr->toWrite = toWrite; + ResetEvent(infoPtr->evWritable); + TclPipeThreadSignal(&infoPtr->writeTI); + bytesWritten = (DWORD) toWrite; + + } else { + /* + * In the blocking case, just try to write the buffer directly. This + * avoids an unnecessary copy. + */ + + if (!SerialBlockingWrite(infoPtr, (LPVOID) buf, (DWORD) toWrite, + &bytesWritten, &infoPtr->osWrite)) { + goto writeError; + } + if (bytesWritten != (DWORD) toWrite) { + /* + * Write timeout. + */ + infoPtr->lastError |= CE_PTO; + errno = EIO; + goto error; + } + } + + return (int) bytesWritten; + + writeError: + Tcl_WinConvertError(GetLastError()); + + error: + /* + * Reset the output queue counter on error during blocking output + */ + + /* + * EnterCriticalSection(&infoPtr->csWrite); + * infoPtr->writeQueue = 0; + * LeaveCriticalSection(&infoPtr->csWrite); + */ + error1: + *errorCode = errno; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * SerialEventProc -- + * + * This function is invoked by Tcl_ServiceEvent when a file event reaches + * the front of the event queue. This procedure invokes Tcl_NotifyChannel + * on the serial. + * + * Results: + * Returns 1 if the event was handled, meaning it should be removed from + * the queue. Returns 0 if the event was not handled, meaning it should + * stay on the queue. The only time the event isn't handled is if the + * TCL_FILE_EVENTS flag bit isn't set. + * + * Side effects: + * Whatever the notifier callback does. + * + *---------------------------------------------------------------------- + */ + +static int +SerialEventProc( + Tcl_Event *evPtr, /* Event to service. */ + int flags) /* Flags that indicate what events to handle, + * such as TCL_FILE_EVENTS. */ +{ + SerialEvent *serialEvPtr = (SerialEvent *)evPtr; + SerialInfo *infoPtr; + int mask; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!(flags & TCL_FILE_EVENTS)) { + return 0; + } + + /* + * Search through the list of watched serials for the one whose handle + * matches the event. We do this rather than simply dereferencing the + * handle in the event so that serials can be deleted while the event is + * in the queue. + */ + + for (infoPtr = tsdPtr->firstSerialPtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (serialEvPtr->infoPtr == infoPtr) { + infoPtr->flags &= ~(SERIAL_PENDING); + break; + } + } + + /* + * Remove stale events. + */ + + if (!infoPtr) { + return 1; + } + + /* + * Check to see if the serial is readable. Note that we can't tell if a + * serial is writable, so we always report it as being writable unless we + * have detected EOF. + */ + + mask = 0; + if (infoPtr->watchMask & TCL_WRITABLE) { + if (infoPtr->writable) { + mask |= TCL_WRITABLE; + infoPtr->writable = 0; + } + } + + if (infoPtr->watchMask & TCL_READABLE) { + if (infoPtr->readable) { + mask |= TCL_READABLE; + infoPtr->readable = 0; + } + } + + /* + * Inform the channel of the events. + */ + + Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask); + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * SerialWatchProc -- + * + * Called by the notifier to set up to watch for events on this channel. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +SerialWatchProc( + void *instanceData, /* Serial state. */ + int mask) /* What events to watch for, OR-ed combination + * of TCL_READABLE, TCL_WRITABLE and + * TCL_EXCEPTION. */ +{ + SerialInfo **nextPtrPtr, *ptr; + SerialInfo *infoPtr = (SerialInfo *) instanceData; + int oldMask = infoPtr->watchMask; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * Since the file is always ready for events, we set the block time so we + * will poll. + */ + + infoPtr->watchMask = mask & infoPtr->validMask; + if (infoPtr->watchMask) { + if (!oldMask) { + infoPtr->nextPtr = tsdPtr->firstSerialPtr; + tsdPtr->firstSerialPtr = infoPtr; + } + SerialBlockTime(infoPtr->blockTime); + } else if (oldMask) { + /* + * Remove the serial port from the list of watched serial ports. + */ + + for (nextPtrPtr=&(tsdPtr->firstSerialPtr), ptr=*nextPtrPtr; ptr!=NULL; + nextPtrPtr=&ptr->nextPtr, ptr=*nextPtrPtr) { + if (infoPtr == ptr) { + *nextPtrPtr = ptr->nextPtr; + break; + } + } + } +} + +/* + *---------------------------------------------------------------------- + * + * SerialGetHandleProc -- + * + * Called from Tcl_GetChannelHandle to retrieve OS handles from inside a + * command serial port based channel. + * + * Results: + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no + * handle for the specified direction. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +SerialGetHandleProc( + void *instanceData, /* The serial state. */ + TCL_UNUSED(int) /*direction*/, + void **handlePtr) /* Where to store the handle. */ +{ + SerialInfo *infoPtr = (SerialInfo *) instanceData; + + *handlePtr = (void *)infoPtr->handle; + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * SerialWriterThread -- + * + * This function runs in a separate thread and writes data onto a serial. + * + * Results: + * Always returns 0. + * + * Side effects: + * Signals the main thread when an output operation is completed. May + * cause the main thread to wake up by posting a message. + * + *---------------------------------------------------------------------- + */ + +static DWORD WINAPI +SerialWriterThread( + LPVOID arg) +{ + TclPipeThreadInfo *pipeTI = (TclPipeThreadInfo *)arg; + SerialInfo *infoPtr = NULL; /* access info only after success init/wait */ + DWORD bytesWritten, toWrite; + char *buf; + OVERLAPPED myWrite; /* Have an own OVERLAPPED in this thread. */ + + for (;;) { + /* + * Wait for the main thread to signal before attempting to write. + */ + if (!TclPipeThreadWaitForSignal(&pipeTI)) { + /* exit */ + break; + } + infoPtr = (SerialInfo *) pipeTI->clientData; + + buf = infoPtr->writeBuf; + toWrite = infoPtr->toWrite; + + myWrite.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL); + + /* + * Loop until all of the bytes are written or an error occurs. + */ + + while (toWrite > 0) { + /* + * Check for pending writeError. Ignore all write operations until + * the user has been notified. + */ + + if (infoPtr->writeError) { + break; + } + if (SerialBlockingWrite(infoPtr, (LPVOID) buf, (DWORD) toWrite, + &bytesWritten, &myWrite) == FALSE) { + infoPtr->writeError = GetLastError(); + break; + } + if (bytesWritten != toWrite) { + /* + * Write timeout. + */ + + infoPtr->writeError = ERROR_WRITE_FAULT; + break; + } + toWrite -= bytesWritten; + buf += bytesWritten; + } + + CloseHandle(myWrite.hEvent); + + /* + * Signal the main thread by signalling the evWritable event and then + * waking up the notifier thread. + */ + + SetEvent(infoPtr->evWritable); + + /* + * Alert the foreground thread. Note that we need to treat this like a + * critical section so the foreground thread does not terminate this + * thread while we are holding a mutex in the notifier code. + */ + + Tcl_MutexLock(&serialMutex); + if (infoPtr->threadId != NULL) { + /* + * TIP #218: When in flight ignore the event, no one will receive + * it anyway. + */ + + Tcl_ThreadAlert(infoPtr->threadId); + } + Tcl_MutexUnlock(&serialMutex); + } + + /* + * We're about to close, so do any drain or discard required. + */ + + if (infoPtr) { + switch (infoPtr->flags & SERIAL_CLOSE_MASK) { + case SERIAL_CLOSE_DRAIN: + FlushFileBuffers(infoPtr->handle); + break; + case SERIAL_CLOSE_DISCARD: + PurgeComm(infoPtr->handle, PURGE_TXABORT | PURGE_TXCLEAR); + break; + } + } + + /* + * Worker exit, so inform the main thread or free TI-structure (if owned). + */ + + TclPipeThreadExit(&pipeTI); + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclWinSerialOpen -- + * + * Opens or Reopens the serial port with the OVERLAPPED FLAG set + * + * Results: + * Returns the new handle, or INVALID_HANDLE_VALUE. + * If an existing channel is specified it is closed and reopened. + * + * Side effects: + * May close/reopen the original handle + * + *---------------------------------------------------------------------- + */ + +HANDLE +TclWinSerialOpen( + HANDLE handle, + const WCHAR *name, + DWORD access) +{ + SerialInit(); + + /* + * If an open channel is specified, close it + */ + + if (handle != INVALID_HANDLE_VALUE && CloseHandle(handle) == FALSE) { + return INVALID_HANDLE_VALUE; + } + + /* + * Multithreaded I/O needs the overlapped flag set otherwise + * ClearCommError blocks under Windows NT/2000 until serial output is + * finished + */ + + handle = CreateFileW(name, access, 0, 0, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, 0); + + return handle; +} + +/* + *---------------------------------------------------------------------- + * + * TclWinOpenSerialChannel -- + * + * Constructs a Serial port channel for the specified standard OS handle. + * This is a helper function to break up the construction of channels + * into File, Console, or Serial. + * + * Results: + * Returns the new channel, or NULL. + * + * Side effects: + * May open the channel + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +TclWinOpenSerialChannel( + HANDLE handle, + char *channelName, + int permissions) +{ + SerialInfo *infoPtr; + + SerialInit(); + + infoPtr = (SerialInfo *)Tcl_Alloc(sizeof(SerialInfo)); + memset(infoPtr, 0, sizeof(SerialInfo)); + + infoPtr->validMask = permissions & (TCL_READABLE|TCL_WRITABLE); + infoPtr->handle = handle; + infoPtr->channel = (Tcl_Channel) NULL; + infoPtr->readable = 0; + infoPtr->writable = 1; + infoPtr->toWrite = infoPtr->writeQueue = 0; + infoPtr->blockTime = SERIAL_DEFAULT_BLOCKTIME; + infoPtr->lastEventTime = 0; + infoPtr->lastError = infoPtr->error = 0; + infoPtr->threadId = Tcl_GetCurrentThread(); + infoPtr->sysBufRead = 4096; + infoPtr->sysBufWrite = 4096; + + /* + * Use the pointer to keep the channel names unique, in case the handles + * are shared between multiple channels (stdin/stdout). + */ + + TclWinGenerateChannelName(channelName, "file", infoPtr); + infoPtr->channel = Tcl_CreateChannel(&serialChannelType, channelName, + infoPtr, permissions); + + SetupComm(handle, infoPtr->sysBufRead, infoPtr->sysBufWrite); + PurgeComm(handle, + PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR); + + /* + * Default is blocking. + */ + + SetCommTimeouts(handle, &no_timeout); + + InitializeCriticalSection(&infoPtr->csWrite); + if (permissions & TCL_READABLE) { + infoPtr->osRead.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL); + } + if (permissions & TCL_WRITABLE) { + /* + * Initially the channel is writable and the writeThread is idle. + */ + + infoPtr->osWrite.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL); + infoPtr->evWritable = CreateEventW(NULL, TRUE, TRUE, NULL); + infoPtr->writeThread = CreateThread(NULL, 256, SerialWriterThread, + TclPipeThreadCreateTI(&infoPtr->writeTI, infoPtr), 0, NULL); + } + + Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); + + return infoPtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * SerialErrorStr -- + * + * Converts a Win32 serial error code to a list of readable errors. + * + * Results: + * None. + * + * Side effects: + * Generates readable errors in the supplied DString. + * + *---------------------------------------------------------------------- + */ + +static void +SerialErrorStr( + DWORD error, /* Win32 serial error code. */ + Tcl_DString *dsPtr) /* Where to store string. */ +{ + if (error & CE_RXOVER) { + Tcl_DStringAppendElement(dsPtr, "RXOVER"); + } + if (error & CE_OVERRUN) { + Tcl_DStringAppendElement(dsPtr, "OVERRUN"); + } + if (error & CE_RXPARITY) { + Tcl_DStringAppendElement(dsPtr, "RXPARITY"); + } + if (error & CE_FRAME) { + Tcl_DStringAppendElement(dsPtr, "FRAME"); + } + if (error & CE_BREAK) { + Tcl_DStringAppendElement(dsPtr, "BREAK"); + } + if (error & CE_TXFULL) { + Tcl_DStringAppendElement(dsPtr, "TXFULL"); + } + if (error & CE_PTO) { /* PTO used to signal WRITE-TIMEOUT */ + Tcl_DStringAppendElement(dsPtr, "TIMEOUT"); + } + if (error & ~((DWORD) (SERIAL_READ_ERRORS | SERIAL_WRITE_ERRORS))) { + char buf[TCL_INTEGER_SPACE + 1]; + + snprintf(buf, sizeof(buf), "%ld", error); + Tcl_DStringAppendElement(dsPtr, buf); + } +} + +/* + *---------------------------------------------------------------------- + * + * SerialModemStatusStr -- + * + * Converts a Win32 modem status list of readable flags + * + * Result: + * None. + * + * Side effects: + * Appends modem status flag strings to the given DString. + * + *---------------------------------------------------------------------- + */ + +static void +SerialModemStatusStr( + DWORD status, /* Win32 modem status. */ + Tcl_DString *dsPtr) /* Where to store string. */ +{ + Tcl_DStringAppendElement(dsPtr, "CTS"); + Tcl_DStringAppendElement(dsPtr, (status & MS_CTS_ON) ? "1" : "0"); + Tcl_DStringAppendElement(dsPtr, "DSR"); + Tcl_DStringAppendElement(dsPtr, (status & MS_DSR_ON) ? "1" : "0"); + Tcl_DStringAppendElement(dsPtr, "RING"); + Tcl_DStringAppendElement(dsPtr, (status & MS_RING_ON) ? "1" : "0"); + Tcl_DStringAppendElement(dsPtr, "DCD"); + Tcl_DStringAppendElement(dsPtr, (status & MS_RLSD_ON) ? "1" : "0"); +} + +/* + *---------------------------------------------------------------------- + * + * SerialSetOptionProc -- + * + * Sets an option on a channel. + * + * Results: + * A standard Tcl result. Also sets the interp's result on error if + * interp is not NULL. + * + * Side effects: + * May modify an option on a device. + * + *---------------------------------------------------------------------- + */ + +static int +SerialSetOptionProc( + void *instanceData, /* Serial state. */ + Tcl_Interp *interp, /* For error reporting - can be NULL. */ + const char *optionName, /* Which option to set? */ + const char *value) /* New value for option. */ +{ + SerialInfo *infoPtr = (SerialInfo *) instanceData; + DCB dcb; + BOOL result, flag; + size_t len, vlen; + Tcl_DString ds; + const WCHAR *native; + Tcl_Size argc; + const char **argv; + + /* + * Parse options. This would be far easier if we had Tcl_Objs to work with + * as that would let us use Tcl_GetIndexFromObj()... + */ + + len = strlen(optionName); + vlen = strlen(value); + + /* + * Option -closemode drain|discard|default + */ + + if ((len > 2) && (strncmp(optionName, "-closemode", len) == 0)) { + if (strncasecmp(value, "DEFAULT", vlen) == 0) { + infoPtr->flags &= ~SERIAL_CLOSE_MASK; + } else if (strncasecmp(value, "DRAIN", vlen) == 0) { + infoPtr->flags &= ~SERIAL_CLOSE_MASK; + infoPtr->flags |= SERIAL_CLOSE_DRAIN; + } else if (strncasecmp(value, "DISCARD", vlen) == 0) { + infoPtr->flags &= ~SERIAL_CLOSE_MASK; + infoPtr->flags |= SERIAL_CLOSE_DISCARD; + } else { + if (interp) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad mode \"%s\" for -closemode: must be" + " default, discard, or drain", value)); + Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", + "VALUE", (char *)NULL); + } + return TCL_ERROR; + } + return TCL_OK; + } + + /* + * Option -mode baud,parity,databits,stopbits + */ + + if ((len > 2) && (strncmp(optionName, "-mode", len) == 0)) { + if (!GetCommState(infoPtr->handle, &dcb)) { + goto getStateFailed; + } + Tcl_DStringInit(&ds); + native = Tcl_UtfToWCharDString(value, TCL_INDEX_NONE, &ds); + result = BuildCommDCBW(native, &dcb); + Tcl_DStringFree(&ds); + + if (result == FALSE) { + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad value \"%s\" for -mode: should be baud,parity,data,stop", + value)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "SERIALMODE", (char *)NULL); + } + return TCL_ERROR; + } + + /* + * Default settings for serial communications. + */ + + dcb.fBinary = TRUE; + dcb.fErrorChar = FALSE; + dcb.fNull = FALSE; + dcb.fAbortOnError = FALSE; + + if (!SetCommState(infoPtr->handle, &dcb)) { + goto setStateFailed; + } + return TCL_OK; + } + + /* + * Option -handshake none|xonxoff|rtscts|dtrdsr + */ + + if ((len > 1) && (strncmp(optionName, "-handshake", len) == 0)) { + if (!GetCommState(infoPtr->handle, &dcb)) { + goto getStateFailed; + } + + /* + * Reset all handshake options. DTR and RTS are ON by default. + */ + + dcb.fOutX = dcb.fInX = FALSE; + dcb.fOutxCtsFlow = dcb.fOutxDsrFlow = dcb.fDsrSensitivity = FALSE; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + dcb.fTXContinueOnXoff = FALSE; + + /* + * Adjust the handshake limits. Yes, the XonXoff limits seem to + * influence even hardware handshake. + */ + + dcb.XonLim = (WORD) (infoPtr->sysBufRead*1/2); + dcb.XoffLim = (WORD) (infoPtr->sysBufRead*1/4); + + if (strncasecmp(value, "NONE", vlen) == 0) { + /* + * Leave all handshake options disabled. + */ + } else if (strncasecmp(value, "XONXOFF", vlen) == 0) { + dcb.fOutX = dcb.fInX = TRUE; + } else if (strncasecmp(value, "RTSCTS", vlen) == 0) { + dcb.fOutxCtsFlow = TRUE; + dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; + } else if (strncasecmp(value, "DTRDSR", vlen) == 0) { + dcb.fOutxDsrFlow = TRUE; + dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; + } else { + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad value \"%s\" for -handshake: must be one of" + " xonxoff, rtscts, dtrdsr or none", value)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "HANDSHAKE", (char *)NULL); + } + return TCL_ERROR; + } + + if (!SetCommState(infoPtr->handle, &dcb)) { + goto setStateFailed; + } + return TCL_OK; + } + + /* + * Option -xchar {\x11 \x13} + */ + + if ((len > 1) && (strncmp(optionName, "-xchar", len) == 0)) { + if (!GetCommState(infoPtr->handle, &dcb)) { + goto getStateFailed; + } + + if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { + return TCL_ERROR; + } + if (argc != 2) { + badXchar: + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "bad value for -xchar: should be a list of" + " two elements with each a single 8-bit character", + TCL_INDEX_NONE)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "XCHAR", (char *)NULL); + } + Tcl_Free((void *)argv); + return TCL_ERROR; + } + + /* + * These dereferences are safe, even in the zero-length string cases, + * because that just makes the xon/xoff character into NUL. When the + * character looks like it is UTF-8 encoded, decode it before casting + * into the format required for the Win guts. Note that this does not + * convert character sets; it is expected that when people set the + * control characters to something large and custom, they'll know the + * hex/octal value rather than the printable form. + */ + + dcb.XonChar = argv[0][0]; + dcb.XoffChar = argv[1][0]; + if (argv[0][0] & 0x80 || argv[1][0] & 0x80) { + Tcl_UniChar character = 0; + int charLen; + + charLen = TclUtfToUniChar(argv[0], &character); + if ((character > 0xFF) || argv[0][charLen]) { + goto badXchar; + } + dcb.XonChar = (char) character; + charLen = TclUtfToUniChar(argv[1], &character); + if ((character > 0xFF) || argv[1][charLen]) { + goto badXchar; + } + dcb.XoffChar = (char) character; + } + Tcl_Free((void *)argv); + + if (!SetCommState(infoPtr->handle, &dcb)) { + goto setStateFailed; + } + return TCL_OK; + } + + /* + * Option -ttycontrol {DTR 1 RTS 0 BREAK 0} + */ + + if ((len > 4) && (strncmp(optionName, "-ttycontrol", len) == 0)) { + Tcl_Size i; + int res = TCL_OK; + + if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { + return TCL_ERROR; + } + if ((argc % 2) == 1) { + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad value \"%s\" for -ttycontrol: should be " + "a list of signal,value pairs", value)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "TTYCONTROL", (char *)NULL); + } + Tcl_Free((void *)argv); + return TCL_ERROR; + } + + for (i = 0; i < argc - 1; i += 2) { + if (Tcl_GetBoolean(interp, argv[i+1], &flag) == TCL_ERROR) { + res = TCL_ERROR; + break; + } + if (strncasecmp(argv[i], "DTR", strlen(argv[i])) == 0) { + if (!EscapeCommFunction(infoPtr->handle, + (DWORD) (flag ? SETDTR : CLRDTR))) { + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "can't set DTR signal", TCL_INDEX_NONE)); + Tcl_SetErrorCode(interp, "TCL", "OPERATION", + "FCONFIGURE", "TTY_SIGNAL", (char *)NULL); + } + res = TCL_ERROR; + break; + } + } else if (strncasecmp(argv[i], "RTS", strlen(argv[i])) == 0) { + if (!EscapeCommFunction(infoPtr->handle, + (DWORD) (flag ? SETRTS : CLRRTS))) { + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "can't set RTS signal", TCL_INDEX_NONE)); + Tcl_SetErrorCode(interp, "TCL", "OPERATION", + "FCONFIGURE", "TTY_SIGNAL", (char *)NULL); + } + res = TCL_ERROR; + break; + } + } else if (strncasecmp(argv[i], "BREAK", strlen(argv[i])) == 0) { + if (!EscapeCommFunction(infoPtr->handle, + (DWORD) (flag ? SETBREAK : CLRBREAK))) { + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "can't set BREAK signal", TCL_INDEX_NONE)); + Tcl_SetErrorCode(interp, "TCL", "OPERATION", + "FCONFIGURE", "TTY_SIGNAL", (char *)NULL); + } + res = TCL_ERROR; + break; + } + } else { + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad signal name \"%s\" for -ttycontrol: must be" + " DTR, RTS or BREAK", argv[i])); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "TTY_SIGNAL", + (char *)NULL); + } + res = TCL_ERROR; + break; + } + } + + Tcl_Free((void *)argv); + return res; + } + + /* + * Option -sysbuffer {read_size write_size} + * Option -sysbuffer read_size + */ + + if ((len > 1) && (strncmp(optionName, "-sysbuffer", len) == 0)) { + /* + * -sysbuffer 4096 or -sysbuffer {64536 4096} + */ + + int inSize = -1, outSize = -1; + + if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { + return TCL_ERROR; + } + if (argc == 1) { + inSize = atoi(argv[0]); + outSize = infoPtr->sysBufWrite; + } else if (argc == 2) { + inSize = atoi(argv[0]); + outSize = atoi(argv[1]); + } + Tcl_Free((void *)argv); + + if ((argc < 1) || (argc > 2) || (inSize <= 0) || (outSize <= 0)) { + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad value \"%s\" for -sysbuffer: should be " + "a list of one or two integers > 0", value)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "SYS_BUFFER", (char *)NULL); + } + return TCL_ERROR; + } + + if (!SetupComm(infoPtr->handle, inSize, outSize)) { + if (interp != NULL) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't setup comm buffers: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + infoPtr->sysBufRead = inSize; + infoPtr->sysBufWrite = outSize; + + /* + * Adjust the handshake limits. Yes, the XonXoff limits seem to + * influence even hardware handshake. + */ + + if (!GetCommState(infoPtr->handle, &dcb)) { + goto getStateFailed; + } + dcb.XonLim = (WORD) (infoPtr->sysBufRead*1/2); + dcb.XoffLim = (WORD) (infoPtr->sysBufRead*1/4); + if (!SetCommState(infoPtr->handle, &dcb)) { + goto setStateFailed; + } + return TCL_OK; + } + + /* + * Option -pollinterval msec + */ + + if ((len > 1) && (strncmp(optionName, "-pollinterval", len) == 0)) { + if (Tcl_GetInt(interp, value, &(infoPtr->blockTime)) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; + } + + /* + * Option -timeout msec + */ + + if ((len > 2) && (strncmp(optionName, "-timeout", len) == 0)) { + int msec; + COMMTIMEOUTS tout = {0,0,0,0,0}; + + if (Tcl_GetInt(interp, value, &msec) != TCL_OK) { + return TCL_ERROR; + } + tout.ReadTotalTimeoutConstant = msec; + if (!SetCommTimeouts(infoPtr->handle, &tout)) { + if (interp != NULL) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't set comm timeouts: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + + return TCL_OK; + } + + return Tcl_BadChannelOption(interp, optionName, + "closemode mode handshake pollinterval sysbuffer timeout " + "ttycontrol xchar"); + + getStateFailed: + if (interp != NULL) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't get comm state: %s", Tcl_PosixError(interp))); + } + return TCL_ERROR; + + setStateFailed: + if (interp != NULL) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't set comm state: %s", Tcl_PosixError(interp))); + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * SerialGetOptionProc -- + * + * Gets a mode associated with an IO channel. If the optionName arg is + * non NULL, retrieves the value of that option. If the optionName arg is + * NULL, retrieves a list of alternating option names and values for the + * given channel. + * + * Results: + * A standard Tcl result. Also sets the supplied DString to the string + * value of the option(s) returned. + * + * Side effects: + * The string returned by this function is in static storage and may be + * reused at any time subsequent to the call. + * + *---------------------------------------------------------------------- + */ + +static int +SerialGetOptionProc( + void *instanceData, /* Serial state. */ + Tcl_Interp *interp, /* For error reporting - can be NULL. */ + const char *optionName, /* Option to get. */ + Tcl_DString *dsPtr) /* Where to store value(s). */ +{ + SerialInfo *infoPtr = (SerialInfo *) instanceData; + DCB dcb; + size_t len; + int valid = 0; /* Flag if valid option parsed. */ + + if (optionName == NULL) { + len = 0; + } else { + len = strlen(optionName); + } + + /* + * Get option -closemode + */ + + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-closemode"); + } + if (len==0 || (len>1 && strncmp(optionName, "-closemode", len)==0)) { + switch (infoPtr->flags & SERIAL_CLOSE_MASK) { + case SERIAL_CLOSE_DRAIN: + Tcl_DStringAppendElement(dsPtr, "drain"); + break; + case SERIAL_CLOSE_DISCARD: + Tcl_DStringAppendElement(dsPtr, "discard"); + break; + default: + Tcl_DStringAppendElement(dsPtr, "default"); + break; + } + } + + /* + * Get option -mode + */ + + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-mode"); + } + if (len==0 || (len>2 && (strncmp(optionName, "-mode", len) == 0))) { + char parity; + const char *stop; + char buf[2 * TCL_INTEGER_SPACE + 16]; + + if (!GetCommState(infoPtr->handle, &dcb)) { + if (interp != NULL) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't get comm state: %s", Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + + valid = 1; + parity = 'n'; + if (dcb.Parity <= 4) { + parity = "noems"[dcb.Parity]; + } + stop = (dcb.StopBits == ONESTOPBIT) ? "1" : + (dcb.StopBits == ONE5STOPBITS) ? "1.5" : "2"; + + snprintf(buf, sizeof(buf), "%ld,%c,%d,%s", dcb.BaudRate, parity, + dcb.ByteSize, stop); + Tcl_DStringAppendElement(dsPtr, buf); + } + + /* + * Get option -pollinterval + */ + + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-pollinterval"); + } + if (len==0 || (len>1 && strncmp(optionName, "-pollinterval", len)==0)) { + char buf[TCL_INTEGER_SPACE + 1]; + + valid = 1; + snprintf(buf, sizeof(buf), "%d", infoPtr->blockTime); + Tcl_DStringAppendElement(dsPtr, buf); + } + + /* + * Get option -sysbuffer + */ + + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-sysbuffer"); + Tcl_DStringStartSublist(dsPtr); + } + if (len==0 || (len>1 && strncmp(optionName, "-sysbuffer", len) == 0)) { + char buf[TCL_INTEGER_SPACE + 1]; + valid = 1; + + snprintf(buf, sizeof(buf), "%ld", infoPtr->sysBufRead); + Tcl_DStringAppendElement(dsPtr, buf); + snprintf(buf, sizeof(buf), "%ld", infoPtr->sysBufWrite); + Tcl_DStringAppendElement(dsPtr, buf); + } + if (len == 0) { + Tcl_DStringEndSublist(dsPtr); + } + + /* + * Get option -xchar + */ + + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-xchar"); + Tcl_DStringStartSublist(dsPtr); + } + if (len==0 || (len>1 && strncmp(optionName, "-xchar", len) == 0)) { + char buf[4]; + valid = 1; + + if (!GetCommState(infoPtr->handle, &dcb)) { + if (interp != NULL) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't get comm state: %s", Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + buf[Tcl_UniCharToUtf(UCHAR(dcb.XonChar), buf)] = '\0'; + Tcl_DStringAppendElement(dsPtr, buf); + buf[Tcl_UniCharToUtf(UCHAR(dcb.XoffChar), buf)] = '\0'; + Tcl_DStringAppendElement(dsPtr, buf); + } + if (len == 0) { + Tcl_DStringEndSublist(dsPtr); + } + + /* + * Get option -lasterror + * + * Option is readonly and returned by [fconfigure chan -lasterror] but not + * returned by unnamed [fconfigure chan]. + */ + + if (len>1 && strncmp(optionName, "-lasterror", len)==0) { + valid = 1; + SerialErrorStr(infoPtr->lastError, dsPtr); + } + + /* + * get option -queue + * + * Option is readonly and returned by [fconfigure chan -queue]. + */ + + if (len>1 && strncmp(optionName, "-queue", len)==0) { + char buf[TCL_INTEGER_SPACE + 1]; + COMSTAT cStat; + DWORD error; + int inBuffered, outBuffered, count; + + valid = 1; + + /* + * Query the pending data in Tcl's internal queues. + */ + + inBuffered = Tcl_InputBuffered(infoPtr->channel); + outBuffered = Tcl_OutputBuffered(infoPtr->channel); + + /* + * Query the number of bytes in our output queue: + * 1. The bytes pending in the output thread + * 2. The bytes in the system drivers buffer + * The writer thread should not interfere this action. + */ + + EnterCriticalSection(&infoPtr->csWrite); + ClearCommError(infoPtr->handle, &error, &cStat); + count = (int) cStat.cbOutQue + infoPtr->writeQueue; + LeaveCriticalSection(&infoPtr->csWrite); + + snprintf(buf, sizeof(buf), "%ld", inBuffered + cStat.cbInQue); + Tcl_DStringAppendElement(dsPtr, buf); + snprintf(buf, sizeof(buf), "%d", outBuffered + count); + Tcl_DStringAppendElement(dsPtr, buf); + } + + /* + * get option -ttystatus + * + * Option is readonly and returned by [fconfigure chan -ttystatus] but not + * returned by unnamed [fconfigure chan]. + */ + + if (len>4 && strncmp(optionName, "-ttystatus", len)==0) { + DWORD status; + + if (!GetCommModemStatus(infoPtr->handle, &status)) { + if (interp != NULL) { + Tcl_WinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't get tty status: %s", Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + valid = 1; + SerialModemStatusStr(status, dsPtr); + } + + if (valid) { + return TCL_OK; + } + return Tcl_BadChannelOption(interp, optionName, + "closemode mode pollinterval lasterror queue sysbuffer ttystatus " + "xchar"); +} + +/* + *---------------------------------------------------------------------- + * + * SerialThreadActionProc -- + * + * Insert or remove any thread local refs to this channel. + * + * Results: + * None. + * + * Side effects: + * Changes thread local list of valid channels. + * + *---------------------------------------------------------------------- + */ + +static void +SerialThreadActionProc( + void *instanceData, + int action) +{ + SerialInfo *infoPtr = (SerialInfo *) instanceData; + + /* + * We do not access firstSerialPtr in the thread structures. This is not + * for all serials managed by the thread, but only those we are watching. + * Removal of the fileevent handlers before transfer thus takes care of + * this structure. + */ + + Tcl_MutexLock(&serialMutex); + if (action == TCL_CHANNEL_THREAD_INSERT) { + /* + * We can't copy the thread information from the channel when the + * channel is created. At this time the channel back pointer has not + * been set yet. However in that case the threadId has already been + * set by TclpCreateCommandChannel itself, so the structure is still + * good. + */ + + SerialInit(); + if (infoPtr->channel != NULL) { + infoPtr->threadId = Tcl_GetChannelThread(infoPtr->channel); + } + } else { + infoPtr->threadId = NULL; + } + Tcl_MutexUnlock(&serialMutex); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclWinSock.c b/vendor/tcl/win/tclWinSock.c new file mode 100644 index 00000000..d4c77f7a --- /dev/null +++ b/vendor/tcl/win/tclWinSock.c @@ -0,0 +1,3350 @@ +/* + * tclWinSock.c -- + * + * This file contains Windows-specific socket related code. + * + * 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. + * + * ----------------------------------------------------------------------- + * The order and naming of functions in this file should minimize + * the file diff to tclUnixSock.c. + * ----------------------------------------------------------------------- + * + * General information on how this module works. + * + * - Each Tcl-thread with its sockets maintains an internal window to receive + * socket messages from the OS. + * + * - To ensure that message reception is always running this window is + * actually owned and handled by an internal thread. This we call the + * co-thread of Tcl's thread. + * + * - The whole structure is set up by InitSockets() which is called for each + * Tcl thread. The implementation of the co-thread is in SocketThread(), + * and the messages are handled by SocketProc(). The connection between + * both is not directly visible, it is done through a Win32 window class. + * This class is initialized by InitSockets() as well, and used in the + * creation of the message receiver windows. + * + * - An important thing to note is that *both* thread and co-thread have + * access to the list of sockets maintained in the private TSD data of the + * thread. The co-thread was given access to it upon creation through the + * new thread's client-data. + * + * Because of this dual access the TSD data contains an OS mutex, the + * "socketListLock", to mediate exclusion between thread and co-thread. + * + * The co-thread's access is all in SocketProc(). The thread's access is + * through SocketEventProc() (1) and the functions called by it. + * + * (Ad 1) This is the handler function for all queued socket events, which + * all the OS messages are translated to through the EventSource (2) + * driven by the OS messages. + * + * (Ad 2) The main functions for this are SocketSetupProc() and + * SocketCheckProc(). + */ + +#include "tclWinInt.h" +#if defined (__clang__) && (__clang_major__ > 20) +#pragma clang diagnostic ignored "-Wc++-keyword" +#endif + +#ifdef _MSC_VER +# pragma comment (lib, "ws2_32") +#endif + +/* + * Helper macros to make parts of this file clearer. The macros do exactly + * what they say on the tin. :-) They also only ever refer to their arguments + * once, and so can be used without regard to side effects. + */ + +#define SET_BITS(var, bits) ((var) |= (bits)) +#define CLEAR_BITS(var, bits) ((var) &= ~(bits)) +#define GOT_BITS(var, bits) (((var) & (bits)) != 0) + +/* "sock" + a pointer in hex + \0 */ +#define SOCK_CHAN_LENGTH (16 + TCL_INTEGER_SPACE) + +/* + * The following variable is used to tell whether this module has been + * initialized. If 1, initialization of sockets was successful, if -1 then + * socket initialization failed (WSAStartup failed). + */ + +static int initialized = 0; +static const WCHAR className[] = L"TclSocket"; +TCL_DECLARE_MUTEX(socketMutex) + +/* + * The following defines declare the messages used on socket windows. + */ +enum TclSocketMessages { + SOCKET_MESSAGE = WM_USER+1, /* Sent by OS: something happened. */ + SOCKET_SELECT = WM_USER+2, /* Adjust select mask. */ + SOCKET_TERMINATE = WM_USER+3/* Stop worker thread. */ +}; + +/* + * Operations used with a SOCKET_SELECT message. + */ +enum SocketSelectOperations { + SELECT = TRUE, /* Add socket to select. */ + UNSELECT = FALSE /* Remove socket from select. */ +}; + +/* + * This is needed to comply with the strict aliasing rules of GCC, but it also + * simplifies casting between the different sockaddr types. + */ + +typedef union { + struct sockaddr sa; + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + struct sockaddr_storage sas; +} address; + +#ifndef IN6_ARE_ADDR_EQUAL +#define IN6_ARE_ADDR_EQUAL IN6_ADDR_EQUAL +#endif + +/* + * This structure describes per-instance state of a tcp-based channel. + */ + +typedef struct TcpState TcpState; + +typedef struct TcpFdList { + TcpState *statePtr; + SOCKET fd; + struct TcpFdList *next; +} TcpFdList; + +struct TcpState { + Tcl_Channel channel; /* Channel associated with this socket. */ + int flags; /* Bit field comprised of the flags described + * below. */ + struct TcpFdList *sockets; /* Windows SOCKET handle. */ + int watchEvents; /* OR'ed combination of FD_READ, FD_WRITE, + * FD_CLOSE, FD_ACCEPT and FD_CONNECT that + * indicate which events are interesting. */ + volatile int readyEvents; /* OR'ed combination of FD_READ, FD_WRITE, + * FD_CLOSE, FD_ACCEPT and FD_CONNECT that + * indicate which events have occurred. + * Set by notifier thread, access must be + * protected by semaphore */ + int selectEvents; /* OR'ed combination of FD_READ, FD_WRITE, + * FD_CLOSE, FD_ACCEPT and FD_CONNECT that + * indicate which events are currently being + * selected. */ + volatile int acceptEventCount; + /* Count of the current number of FD_ACCEPTs + * that have arrived and not yet processed. + * Set by notifier thread, access must be + * protected by semaphore */ + Tcl_TcpAcceptProc *acceptProc; + /* Proc to call on accept. */ + void *acceptProcData; /* The data for the accept proc. */ + + /* + * Only needed for client sockets + */ + + struct addrinfo *addrlist; /* Addresses to connect to. */ + struct addrinfo *addr; /* Iterator over addrlist. */ + struct addrinfo *myaddrlist;/* Local address. */ + struct addrinfo *myaddr; /* Iterator over myaddrlist. */ + int connectError; /* Cache status of async socket. */ + int cachedBlocking; /* Cache blocking mode of async socket. */ + volatile int notifierConnectError; + /* Async connect error set by notifier thread. + * This error is still a windows error code. + * Access must be protected by semaphore */ + struct TcpState *nextPtr; /* The next socket on the per-thread socket + * list. */ +}; + +/* + * These bits may be OR'ed together into the "flags" field of a TcpState + * structure. + */ + +enum TcpStateFlags { + TCP_NONBLOCKING = 1<<0, /* Socket with non-blocking I/O. */ + TCP_ASYNC_CONNECT = 1<<1, /* Async connect in progress. */ + SOCKET_EOF = 1<<2, /* A zero read happened on the socket. */ + SOCKET_PENDING = 1<<3, /* A message has been sent for this socket */ + TCP_ASYNC_PENDING = 1<<4, /* TcpConnect was called to process an async + * connect. This flag indicates that reentry is + * still pending. */ + TCP_ASYNC_FAILED = 1<<5, /* An async connect finally failed. */ + + TCP_ASYNC_TEST_MODE = 1<<8 /* Async testing activated. Do not + * automatically continue connection + * process */ +}; + +/* + * The following structure is what is added to the Tcl event queue when a + * socket event occurs. + */ + +typedef struct { + Tcl_Event header; /* Information that is standard for all + * events. */ + SOCKET socket; /* Socket descriptor that is ready. Used to + * find the TcpState structure for the file + * (can't point directly to the TcpState + * structure because it could go away while + * the event is queued). */ +} SocketEvent; + +/* + * This defines the minimum buffersize maintained by the kernel. + */ + +#define TCP_BUFFER_SIZE 4096 + +/* + * Per (main) thread data, holding list of things being waited upon and the + * various handles to things doing the waiting/notification. + */ +typedef struct { + HWND hwnd; /* Handle to window for socket messages. */ + HANDLE socketThread; /* Thread handling the window */ + Tcl_ThreadId threadId; /* Parent thread. */ + HANDLE readyEvent; /* Event indicating that a socket event is + * ready. Also used to indicate that the + * socketThread has been initialized and has + * started. */ + HANDLE socketListLock; /* Win32 Event to lock the socketList */ + TcpState *pendingTcpState; /* This socket is opened but not jet in the + * list. This value is also checked by + * the event structure. */ + TcpState *socketList; /* Every open socket in this thread has an + * entry on this list. */ +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; +static WNDCLASSW windowClass; + +/* + * Static routines for this file: + */ + +static int TcpConnect(Tcl_Interp *interp, + TcpState *state); +static void InitSocketWindowClass(void); +static TcpState * NewSocketInfo(SOCKET socket); +static void SocketExitHandler(void *clientData); +static LRESULT CALLBACK SocketProc(HWND hwnd, UINT message, WPARAM wParam, + LPARAM lParam); +static void TcpAccept(TcpFdList *fds, SOCKET newSocket, address addr); +static int WaitForConnect(TcpState *statePtr, int *errorCodePtr); +static int WaitForSocketEvent(TcpState *statePtr, int events, + int *errorCodePtr); +static void AddSocketInfoFd(TcpState *statePtr, SOCKET socket); +static int FindFDInList(TcpState *statePtr, SOCKET socket); +static DWORD WINAPI SocketThread(LPVOID arg); +static void TcpThreadActionProc(void *instanceData, + int action); +static int TcpCloseProc(void *, Tcl_Interp *); + +static Tcl_EventCheckProc SocketCheckProc; +static Tcl_EventProc SocketEventProc; +static Tcl_EventSetupProc SocketSetupProc; +static Tcl_DriverBlockModeProc TcpBlockModeProc; +static Tcl_DriverClose2Proc TcpClose2Proc; +static Tcl_DriverSetOptionProc TcpSetOptionProc; +static Tcl_DriverGetOptionProc TcpGetOptionProc; +static Tcl_DriverInputProc TcpInputProc; +static Tcl_DriverOutputProc TcpOutputProc; +static Tcl_DriverWatchProc TcpWatchProc; +static Tcl_DriverGetHandleProc TcpGetHandleProc; + +/* + * This structure describes the channel type structure for TCP socket + * based IO: + */ + +static const Tcl_ChannelType tcpChannelType = { + "tcp", + TCL_CHANNEL_VERSION_5, + NULL, /* Deprecated. */ + TcpInputProc, + TcpOutputProc, + NULL, /* Deprecated. */ + TcpSetOptionProc, + TcpGetOptionProc, + TcpWatchProc, + TcpGetHandleProc, + TcpClose2Proc, + TcpBlockModeProc, + NULL, /* Flush proc. */ + NULL, /* Bubbled event handler proc. */ + NULL, /* Seek proc. */ + TcpThreadActionProc, + NULL /* Truncate proc. */ +}; + +/* + * The following variable holds the network name of this host. + */ + +static TclInitProcessGlobalValueProc InitializeHostName; +static ProcessGlobalValue hostName = + {0, 0, NULL, NULL, InitializeHostName, NULL, NULL}; + +/* + *---------------------------------------------------------------------- + * + * SendSelectMessage -- + * + * Simple wrapper round the SendMessage syscall with a SOCKET_SELECT + * message to add a bit of type safety. + * + *---------------------------------------------------------------------- + */ +static inline void +SendSelectMessage( + ThreadSpecificData *tsdPtr, /* Reference to this thread's worker. */ + int operation, /* Whether to add or remove from the mask. */ + TcpState *payload) /* What socket to add/remove. */ +{ + SendMessageW(tsdPtr->hwnd, SOCKET_SELECT, (WPARAM) operation, + (LPARAM) payload); +} + +/* + * Address print debug functions + */ +#if 0 +static inline void +printaddrinfo( + struct addrinfo *ai, + char *prefix) +{ + char host[NI_MAXHOST], port[NI_MAXSERV]; + + getnameinfo(ai->ai_addr, ai->ai_addrlen, + host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST | NI_NUMERICSERV); +} + +static void +printaddrinfolist( + struct addrinfo *addrlist, + char *prefix) +{ + struct addrinfo *ai; + + for (ai = addrlist; ai != NULL; ai = ai->ai_next) { + printaddrinfo(ai, prefix); + } +} +#endif + +/* + *---------------------------------------------------------------------- + * + * InitializeHostName -- + * + * This routine sets the process global value of the name of the local + * host on which the process is running. + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ + +void +InitializeHostName( + char **valuePtr, + size_t *lengthPtr, + Tcl_Encoding *encodingPtr) +{ + WCHAR wbuf[256]; + DWORD length = sizeof(wbuf) / sizeof(WCHAR); + Tcl_DString ds; + + Tcl_DStringInit(&ds); + if (GetComputerNameExW(ComputerNamePhysicalDnsFullyQualified, wbuf, &length) != 0) { + /* + * Convert string from WCHAR to utf-8, then change to lowercase, + * then to system encoding. + */ + Tcl_DString inDs; + + Tcl_DStringInit(&inDs); + Tcl_UtfToLower(Tcl_WCharToUtfDString(wbuf, TCL_INDEX_NONE, &inDs)); + Tcl_UtfToExternalDStringEx(NULL, NULL, Tcl_DStringValue(&inDs), + TCL_INDEX_NONE, TCL_ENCODING_PROFILE_TCL8, &ds, NULL); + Tcl_DStringFree(&inDs); + } else { + TclInitSockets(); + /* + * The buffer size of 256 is recommended by the MSDN page that + * documents gethostname() as being always adequate. + */ + + Tcl_DStringInit(&ds); + Tcl_DStringSetLength(&ds, 256); + gethostname(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)); + Tcl_DStringSetLength(&ds, strlen(Tcl_DStringValue(&ds))); + } + + *encodingPtr = Tcl_GetEncoding(NULL, NULL); + *lengthPtr = Tcl_DStringLength(&ds); + *valuePtr = (char *)Tcl_Alloc(*lengthPtr + 1); + memcpy(*valuePtr, Tcl_DStringValue(&ds), *lengthPtr + 1); + Tcl_DStringFree(&ds); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_GetHostName -- + * + * Returns the name of the local host. + * + * Results: + * A string containing the network name for this machine, or an empty + * string if we can't figure out the name. The caller must not modify or + * free this string. + * + * Side effects: + * Caches the name to return for future calls. + * + *---------------------------------------------------------------------- + */ + +const char * +Tcl_GetHostName(void) +{ + return Tcl_GetString(TclGetProcessGlobalValue(&hostName)); +} + +/* + *---------------------------------------------------------------------- + * + * TclInitSockets -- + * + * Initialization of sockets for the thread. Also creates message + * handling window class for the process if needed. + * + * Results: + * Nothing. Panics on failure. + * + * Side effects: + * If not already prepared, initializes the TSD structure and socket + * message handling thread associated to the calling thread for the + * subsystem of the driver. + * + *---------------------------------------------------------------------- + */ + +void +TclInitSockets(void) +{ + /* Then Per thread initialization. */ + DWORD id; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + + if (tsdPtr != NULL) { + return; + } + + InitSocketWindowClass(); + + /* + * OK, this thread has never done anything with sockets before. Construct + * a worker thread to handle asynchronous events related to sockets + * assigned to _this_ thread. + */ + + tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->pendingTcpState = NULL; + tsdPtr->socketList = NULL; + tsdPtr->hwnd = NULL; + tsdPtr->threadId = Tcl_GetCurrentThread(); + tsdPtr->readyEvent = CreateEventW(NULL, FALSE, FALSE, NULL); + if (tsdPtr->readyEvent == NULL) { + goto initFailure; + } + tsdPtr->socketListLock = CreateEventW(NULL, FALSE, TRUE, NULL); + if (tsdPtr->socketListLock == NULL) { + goto initFailure; + } + tsdPtr->socketThread = CreateThread(NULL, 256, SocketThread, tsdPtr, 0, + &id); + if (tsdPtr->socketThread == NULL) { + goto initFailure; + } + + SetThreadPriority(tsdPtr->socketThread, THREAD_PRIORITY_HIGHEST); + + /* + * Wait for the thread to signal when the window has been created and if + * it is ready to go. + */ + + WaitForSingleObject(tsdPtr->readyEvent, INFINITE); + + if (tsdPtr->hwnd != NULL) { + Tcl_CreateEventSource(SocketSetupProc, SocketCheckProc, NULL); + return; + } + + initFailure: + Tcl_Panic("InitSockets failed"); + return; +} + +/* + *---------------------------------------------------------------------- + * + * TclpFinalizeSockets -- + * + * This function is called from Tcl_FinalizeThread to finalize the + * platform specific socket subsystem. Also, it may be called from within + * this module to cleanup the state if unable to initialize the sockets + * subsystem. + * + * Results: + * None. + * + * Side effects: + * Deletes the event source and destroys the socket thread. + * + *---------------------------------------------------------------------- + */ + +void +TclpFinalizeSockets(void) +{ + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + + /* + * Careful! This is a finalizer! + */ + + if (tsdPtr == NULL) { + return; + } + + if (tsdPtr->socketThread != NULL) { + if (tsdPtr->hwnd != NULL) { + PostMessageW(tsdPtr->hwnd, SOCKET_TERMINATE, 0, 0); + + /* + * Wait for the thread to exit. This ensures that we are + * completely cleaned up before we leave this function. + */ + + WaitForSingleObject(tsdPtr->socketThread, INFINITE); + tsdPtr->hwnd = NULL; + } + CloseHandle(tsdPtr->socketThread); + tsdPtr->socketThread = NULL; + } + if (tsdPtr->readyEvent != NULL) { + CloseHandle(tsdPtr->readyEvent); + tsdPtr->readyEvent = NULL; + } + if (tsdPtr->socketListLock != NULL) { + CloseHandle(tsdPtr->socketListLock); + tsdPtr->socketListLock = NULL; + } + Tcl_DeleteEventSource(SocketSetupProc, SocketCheckProc, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * TcpBlockModeProc -- + * + * This function is invoked by the generic IO level to set blocking and + * nonblocking mode on a TCP socket based channel. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or nonblocking mode. + * + *---------------------------------------------------------------------- + */ + +static int +TcpBlockModeProc( + void *instanceData, /* Socket state. */ + int mode) /* The mode to set. Can be one of + * TCL_MODE_BLOCKING or + * TCL_MODE_NONBLOCKING. */ +{ + TcpState *statePtr = (TcpState *)instanceData; + + if (mode == TCL_MODE_NONBLOCKING) { + SET_BITS(statePtr->flags, TCP_NONBLOCKING); + } else { + CLEAR_BITS(statePtr->flags, TCP_NONBLOCKING); + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * WaitForConnect -- + * + * Check the state of an async connect process. If a connection attempt + * terminated, process it, which may finalize it or may start the next + * attempt. If a connect error occures, it is saved in + * statePtr->connectError to be reported by 'fconfigure -error'. + * + * There are two modes of operation, defined by errorCodePtr: + * * non-NULL: Called by explicite read/write command. Block if socket + * is blocking. + * May return two error codes: + * * EWOULDBLOCK: if connect is still in progress + * * ENOTCONN: if connect failed. This would be the error message + * of a recv or sendto syscall so this is emulated here. + * * Null: Called by a background operation. Do not block and don't + * return any error code. + * + * Results: + * 0 if the connection has completed, -1 if still in progress or there is + * an error. + * + * Side effects: + * Processes socket events off the system queue. May process + * asynchronous connect. + * + *---------------------------------------------------------------------- + */ + +static int +WaitForConnect( + TcpState *statePtr, /* State of the socket. */ + int *errorCodePtr) /* Where to store errors? A passed + * null-pointer activates background mode. */ +{ + int result; + int oldMode; + + /* + * Check if an async connect failed already and error reporting is + * demanded, return the error ENOTCONN. + */ + + if (errorCodePtr != NULL && GOT_BITS(statePtr->flags, TCP_ASYNC_FAILED)) { + *errorCodePtr = ENOTCONN; + return -1; + } + + /* + * Check if an async connect is running. If not return ok + */ + + if (!GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT)) { + return 0; + } + + /* + * In socket test mode do not continue with the connect + * Exceptions are: + * - Call by recv/send and blocking socket + * (errorCodePtr != NULL && !GOT_BITS(flags, TCP_NONBLOCKING)) + * - Call by the event queue (errorCodePtr == NULL) + */ + + if (GOT_BITS(statePtr->flags, TCP_ASYNC_TEST_MODE) + && errorCodePtr != NULL + && GOT_BITS(statePtr->flags, TCP_NONBLOCKING)) { + *errorCodePtr = EWOULDBLOCK; + return -1; + } + + /* + * Be sure to disable event servicing so we are truly modal. + */ + + oldMode = Tcl_SetServiceMode(TCL_SERVICE_NONE); + + /* + * Loop in the blocking case until the connect signal is present + */ + + while (1) { + /* + * Get the statePtr lock. + */ + + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + + /* + * Check for connect event. + */ + + if (GOT_BITS(statePtr->readyEvents, FD_CONNECT)) { + /* + * Consume the connect event. + */ + + CLEAR_BITS(statePtr->readyEvents, FD_CONNECT); + + /* + * For blocking sockets and foreground processing, disable async + * connect as we continue now synchronously. + */ + + if (errorCodePtr != NULL && + !GOT_BITS(statePtr->flags, TCP_NONBLOCKING)) { + CLEAR_BITS(statePtr->flags, TCP_ASYNC_CONNECT); + } + + /* + * Free list lock. + */ + + SetEvent(tsdPtr->socketListLock); + + /* + * Continue connect. If switched to synchronous connect, the + * connect is terminated. + */ + + result = TcpConnect(NULL, statePtr); + + /* + * Restore event service mode. + */ + + (void) Tcl_SetServiceMode(oldMode); + + /* + * Check for Successful connect or async connect restart + */ + + if (result == TCL_OK) { + /* + * Check for async connect restart (not possible for + * foreground blocking operation) + */ + + if (GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { + if (errorCodePtr != NULL) { + *errorCodePtr = EWOULDBLOCK; + } + return -1; + } + return 0; + } + + /* + * Connect finally failed. For foreground operation return + * ENOTCONN. + */ + + if (errorCodePtr != NULL) { + *errorCodePtr = ENOTCONN; + } + return -1; + } + + /* + * Free list lock. + */ + + SetEvent(tsdPtr->socketListLock); + + /* + * Background operation returns with no action as there was no connect + * event + */ + + if (errorCodePtr == NULL) { + return -1; + } + + /* + * A non blocking socket waiting for an asynchronous connect + * returns directly the error EWOULDBLOCK + */ + + if (GOT_BITS(statePtr->flags, TCP_NONBLOCKING)) { + *errorCodePtr = EWOULDBLOCK; + return -1; + } + + /* + * Wait until something happens. + */ + + WaitForSingleObject(tsdPtr->readyEvent, INFINITE); + } +} + +/* + *---------------------------------------------------------------------- + * + * TcpInputProc -- + * + * This function is invoked by the generic IO level to read input from a + * TCP socket based channel. + * + * Results: + * The number of bytes read is returned or -1 on error. An output + * argument contains the POSIX error code on error, or zero if no error + * occurred. + * + * Side effects: + * Reads input from the input device of the channel. + * + *---------------------------------------------------------------------- + */ + +static int +TcpInputProc( + void *instanceData, /* Socket state. */ + char *buf, /* Where to store data read. */ + int bufSize, /* How much space is available in the + * buffer? */ + int *errorCodePtr) /* Where to store error code. */ +{ + TcpState *statePtr = (TcpState *)instanceData; + int bytesRead; + DWORD error; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + + *errorCodePtr = 0; + + /* + * First check to see if EOF was already detected, to prevent calling the + * socket stack after the first time EOF is detected. + */ + + if (GOT_BITS(statePtr->flags, SOCKET_EOF)) { + return 0; + } + + /* + * Check if there is an async connect running. + * For blocking sockets terminate connect, otherwise do one step. + * For a non blocking socket return EWOULDBLOCK if connect not terminated + */ + + if (WaitForConnect(statePtr, errorCodePtr) != 0) { + return -1; + } + + /* + * No EOF, and it is connected, so try to read more from the socket. Note + * that we clear the FD_READ bit because read events are level triggered + * so a new event will be generated if there is still data available to be + * read. We have to simulate blocking behavior here since we are always + * using non-blocking sockets. + */ + + while (1) { + SendSelectMessage(tsdPtr, UNSELECT, statePtr); + + /* + * Single fd operation: this proc is only called for a connected + * socket. + */ + + bytesRead = recv(statePtr->sockets->fd, buf, bufSize, 0); + CLEAR_BITS(statePtr->readyEvents, FD_READ); + + /* + * Check for end-of-file condition or successful read. + */ + + if (bytesRead == 0) { + SET_BITS(statePtr->flags, SOCKET_EOF); + } + if (bytesRead != SOCKET_ERROR) { + break; + } + + /* + * If an error occurs after the FD_CLOSE has arrived, then ignore the + * error and report an EOF. + */ + + if (GOT_BITS(statePtr->readyEvents, FD_CLOSE)) { + SET_BITS(statePtr->flags, SOCKET_EOF); + bytesRead = 0; + break; + } + + error = WSAGetLastError(); + + /* + * If an RST comes, then ignore the error and report an EOF just like + * on Unix. + */ + + if (error == WSAECONNRESET) { + SET_BITS(statePtr->flags, SOCKET_EOF); + bytesRead = 0; + break; + } + + /* + * Check for error condition or underflow in non-blocking case. + */ + + if (GOT_BITS(statePtr->flags, TCP_NONBLOCKING) + || (error != WSAEWOULDBLOCK)) { + Tcl_WinConvertError(error); + *errorCodePtr = Tcl_GetErrno(); + bytesRead = -1; + break; + } + + /* + * In the blocking case, wait until the file becomes readable or + * closed and try again. + */ + + if (!WaitForSocketEvent(statePtr, FD_READ|FD_CLOSE, errorCodePtr)) { + bytesRead = -1; + break; + } + } + + SendSelectMessage(tsdPtr, SELECT, statePtr); + + return bytesRead; +} + +/* + *---------------------------------------------------------------------- + * + * TcpOutputProc -- + * + * This function is called by the generic IO level to write data to a + * socket based channel. + * + * Results: + * The number of bytes written or -1 on failure. + * + * Side effects: + * Produces output on the socket. + * + *---------------------------------------------------------------------- + */ + +static int +TcpOutputProc( + void *instanceData, /* Socket state. */ + const char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCodePtr) /* Where to store error code. */ +{ + TcpState *statePtr = (TcpState *)instanceData; + int written; + DWORD error; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + + *errorCodePtr = 0; + + /* + * Check if there is an async connect running. + * For blocking sockets terminate connect, otherwise do one step. + * For a non blocking socket return EWOULDBLOCK if connect not terminated + */ + + if (WaitForConnect(statePtr, errorCodePtr) != 0) { + return -1; + } + + while (1) { + SendSelectMessage(tsdPtr, UNSELECT, statePtr); + + /* + * Single fd operation: this proc is only called for a connected + * socket. + */ + + written = send(statePtr->sockets->fd, buf, toWrite, 0); + if (written != SOCKET_ERROR) { + /* + * Since Windows won't generate a new write event until we hit an + * overflow condition, we need to force the event loop to poll + * until the condition changes. + */ + + if (GOT_BITS(statePtr->watchEvents, FD_WRITE)) { + Tcl_Time blockTime = { 0, 0 }; + + Tcl_SetMaxBlockTime(&blockTime); + } + break; + } + + /* + * Check for error condition or overflow. In the event of overflow, we + * need to clear the FD_WRITE flag so we can detect the next writable + * event. Note that Windows only sends a new writable event after a + * send fails with WSAEWOULDBLOCK. + */ + + error = WSAGetLastError(); + if (error == WSAEWOULDBLOCK) { + CLEAR_BITS(statePtr->readyEvents, FD_WRITE); + if (GOT_BITS(statePtr->flags, TCP_NONBLOCKING)) { + *errorCodePtr = EWOULDBLOCK; + written = -1; + break; + } + } else { + Tcl_WinConvertError(error); + *errorCodePtr = Tcl_GetErrno(); + written = -1; + break; + } + + /* + * In the blocking case, wait until the file becomes writable or + * closed and try again. + */ + + if (!WaitForSocketEvent(statePtr, FD_WRITE|FD_CLOSE, errorCodePtr)) { + written = -1; + break; + } + } + + SendSelectMessage(tsdPtr, SELECT, statePtr); + + return written; +} + +/* + *---------------------------------------------------------------------- + * + * TcpCloseProc -- + * + * This function is called by the generic IO level to perform channel + * type specific cleanup on a socket based channel when the channel is + * closed. + * + * Results: + * 0 if successful, the value of errno if failed. + * + * Side effects: + * Closes the socket. + * + *---------------------------------------------------------------------- + */ + +static int +TcpCloseProc( + void *instanceData, /* The socket to close. */ + TCL_UNUSED(Tcl_Interp *)) +{ + TcpState *statePtr = (TcpState *)instanceData; + /* TIP #218 */ + int errorCode = 0; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * Clean up the OS socket handle. The default Windows setting for a + * socket is SO_DONTLINGER, which does a graceful shutdown in the + * background. + */ + + while (statePtr->sockets != NULL) { + TcpFdList *thisfd = statePtr->sockets; + + statePtr->sockets = thisfd->next; + if (closesocket(thisfd->fd) == SOCKET_ERROR) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + errorCode = Tcl_GetErrno(); + } + Tcl_Free(thisfd); + } + + if (statePtr->addrlist != NULL) { + freeaddrinfo(statePtr->addrlist); + } + if (statePtr->myaddrlist != NULL) { + freeaddrinfo(statePtr->myaddrlist); + } + + /* + * Clear an eventual tsd info list pointer. + * + * This may be called, if an async socket connect fails or is closed + * between connect and thread action callback. + */ + + if (tsdPtr->pendingTcpState != NULL + && tsdPtr->pendingTcpState == statePtr) { + /* + * Get infoPtr lock, because this concerns the notifier thread. + */ + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + + tsdPtr->pendingTcpState = NULL; + + /* + * Free list lock. + */ + + SetEvent(tsdPtr->socketListLock); + } + + /* + * TIP #218. Removed the code removing the structure from the global + * socket list. This is now done by the thread action callbacks, and only + * there. This happens before this code is called. We can free without + * fear of damaging the list. + */ + + Tcl_Free(statePtr); + return errorCode; +} + +/* + *---------------------------------------------------------------------- + * + * TcpClose2Proc -- + * + * This function is called by the generic IO level to perform the channel + * type specific part of a half-close: namely, a shutdown() on a socket. + * + * Results: + * 0 if successful, the value of errno if failed. + * + * Side effects: + * Shuts down one side of the socket. + * + *---------------------------------------------------------------------- + */ + +static int +TcpClose2Proc( + void *instanceData, /* The socket to close. */ + Tcl_Interp *interp, /* For error reporting. */ + int flags) /* Flags that indicate which side to close. */ +{ + TcpState *statePtr = (TcpState *)instanceData; + int readError = 0; + int writeError = 0; + + /* + * Shutdown the OS socket handle. + */ + + if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) == 0) { + return TcpCloseProc(instanceData, interp); + } + + /* + * Single fd operation: Tcl_OpenTcpServer() does not set TCL_READABLE or + * TCL_WRITABLE so this should never be called for a server socket. + */ + + if ((flags & TCL_CLOSE_READ) + && (shutdown(statePtr->sockets->fd, SD_RECEIVE) == SOCKET_ERROR)) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + readError = Tcl_GetErrno(); + } + if ((flags & TCL_CLOSE_WRITE) + && (shutdown(statePtr->sockets->fd, SD_SEND) == SOCKET_ERROR)) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + writeError = Tcl_GetErrno(); + } + return (readError != 0) ? readError : writeError; +} + +/* + *---------------------------------------------------------------------- + * + * TcpSetOptionProc -- + * + * Sets Tcp channel specific options. + * + * Results: + * None, unless an error happens. + * + * Side effects: + * Changes attributes of the socket at the system level. + * + *---------------------------------------------------------------------- + */ + +static int +TcpSetOptionProc( + void *instanceData, /* Socket state. */ + Tcl_Interp *interp, /* For error reporting - can be NULL. */ + const char *optionName, /* Name of the option to set. */ + const char *value) /* New value for option. */ +{ + TcpState *statePtr = (TcpState *)instanceData; + SOCKET sock; + size_t len = 0; + + if (optionName != NULL) { + len = strlen(optionName); + } + + sock = statePtr->sockets->fd; + + if ((len > 1) && (optionName[1] == 'k') && + (strncmp(optionName, "-keepalive", len) == 0)) { + BOOL boolVar; + int rtn; + + if (Tcl_GetBoolean(interp, value, &boolVar) != TCL_OK) { + return TCL_ERROR; + } + rtn = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, + (const char *) &boolVar, sizeof(boolVar)); + if (rtn != 0) { + Tcl_WinConvertError(WSAGetLastError()); + if (interp) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't set socket option: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + return TCL_OK; + } + if ((len > 1) && (optionName[1] == 'n') && + (strncmp(optionName, "-nodelay", len) == 0)) { + BOOL boolVar; + int rtn; + + if (Tcl_GetBoolean(interp, value, &boolVar) != TCL_OK) { + return TCL_ERROR; + } + rtn = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + (const char *) &boolVar, sizeof(boolVar)); + if (rtn != 0) { + Tcl_WinConvertError(WSAGetLastError()); + if (interp) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't set socket option: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + return TCL_OK; + } + return Tcl_BadChannelOption(interp, optionName, "keepalive nodelay"); +} + +/* + *---------------------------------------------------------------------- + * + * TcpGetOptionProc -- + * + * Computes an option value for a TCP socket based channel, or a list of + * all options and their values. + * + * Note: This code is based on code contributed by John Haxby. + * + * Results: + * A standard Tcl result. The value of the specified option or a list of + * all options and their values is returned in the supplied DString. Sets + * Error message if needed. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TcpGetOptionProc( + void *instanceData, /* Socket state. */ + Tcl_Interp *interp, /* For error reporting - can be NULL. */ + const char *optionName, /* Name of the option to retrieve the value + * for, or NULL to get all options and their + * values. */ + Tcl_DString *dsPtr) /* Where to store the computed value; + * initialized by caller. */ +{ + TcpState *statePtr = (TcpState *)instanceData; + char host[NI_MAXHOST], port[NI_MAXSERV]; + SOCKET sock; + size_t len = 0; + int reverseDNS = 0; +#define SUPPRESS_RDNS_VAR "::tcl::unsupported::noReverseDNS" +#define HAVE_OPTION(option) \ + ((len > 1) && (optionName[1] == option[1]) && \ + (strncmp(optionName, option, len) == 0)) + + /* + * Go one step in async connect + * + * If any error is thrown save it as background error to report eventually + * below. + */ + + if (!GOT_BITS(statePtr->flags, TCP_ASYNC_TEST_MODE)) { + WaitForConnect(statePtr, NULL); + } + + sock = statePtr->sockets->fd; + if (optionName != NULL) { + len = strlen(optionName); + } + + if (HAVE_OPTION("-error")) { + /* + * Do not return any errors if async connect is running. + */ + + if (!GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { + if (GOT_BITS(statePtr->flags, TCP_ASYNC_FAILED)) { + /* + * In case of a failed async connect, eventually report the + * connect error only once. Do not report the system error, + * as this comes again and again. + */ + + if (statePtr->connectError != 0) { + Tcl_DStringAppend(dsPtr, + Tcl_ErrnoMsg(statePtr->connectError), + TCL_INDEX_NONE); + statePtr->connectError = 0; + } + } else { + /* + * Report an eventual last error of the socket system. + */ + + int optlen; + int ret; + DWORD err; + + /* + * Populate the err variable with a POSIX error + */ + + optlen = sizeof(int); + ret = getsockopt(sock, SOL_SOCKET, SO_ERROR, + (char *)&err, &optlen); + + /* + * The error was not returned directly but should be taken + * from WSA. + */ + + if (ret == SOCKET_ERROR) { + err = WSAGetLastError(); + } + + /* + * Return error message. + */ + + if (err) { + Tcl_WinConvertError(err); + Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(Tcl_GetErrno()), + TCL_INDEX_NONE); + } + } + } + return TCL_OK; + } + + if (HAVE_OPTION("-connecting")) { + Tcl_DStringAppend(dsPtr, + GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING) + ? "1" : "0", TCL_INDEX_NONE); + return TCL_OK; + } + + if (interp != NULL + && Tcl_GetVar(interp, SUPPRESS_RDNS_VAR, 0) != NULL) { + reverseDNS = NI_NUMERICHOST; + } + + if ((len == 0) || HAVE_OPTION("-peername")) { + address peername; + socklen_t size = sizeof(peername); + + if (GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { + /* + * In async connect output an empty string + */ + + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-peername"); + Tcl_DStringAppendElement(dsPtr, ""); + } else { + return TCL_OK; + } + } else if (getpeername(sock, (LPSOCKADDR) &(peername.sa), + &size) == 0) { + /* + * Peername fetch succeeded - output list + */ + + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-peername"); + Tcl_DStringStartSublist(dsPtr); + } + + getnameinfo(&(peername.sa), size, host, sizeof(host), + NULL, 0, NI_NUMERICHOST); + Tcl_DStringAppendElement(dsPtr, host); + getnameinfo(&(peername.sa), size, host, sizeof(host), + port, sizeof(port), reverseDNS | NI_NUMERICSERV); + Tcl_DStringAppendElement(dsPtr, host); + Tcl_DStringAppendElement(dsPtr, port); + if (len == 0) { + Tcl_DStringEndSublist(dsPtr); + } else { + return TCL_OK; + } + } else { + /* + * getpeername failed - but if we were asked for all the options + * (len==0), don't flag an error at that point because it could be + * an fconfigure request on a server socket (such sockets have no + * peer). {Copied from unix/tclUnixChan.c} + */ + + if (len) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + if (interp) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't get peername: %s", + Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + } + } + + if ((len == 0) || HAVE_OPTION("-sockname")) { + TcpFdList *fds; + address sockname; + socklen_t size; + int found = 0; + + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-sockname"); + Tcl_DStringStartSublist(dsPtr); + } + if (GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { + /* + * In async connect output an empty string + */ + + found = 1; + } else { + for (fds = statePtr->sockets; fds != NULL; fds = fds->next) { + sock = fds->fd; + size = sizeof(sockname); + if (getsockname(sock, &(sockname.sa), &size) >= 0) { + int flags = reverseDNS; + + found = 1; + getnameinfo(&sockname.sa, size, host, sizeof(host), + NULL, 0, NI_NUMERICHOST); + Tcl_DStringAppendElement(dsPtr, host); + + /* + * We don't want to resolve INADDR_ANY and sin6addr_any; + * they can sometimes cause problems (and never have a + * name). + */ + + flags |= NI_NUMERICSERV; + if (sockname.sa.sa_family == AF_INET) { + if (sockname.sa4.sin_addr.s_addr == INADDR_ANY) { + flags |= NI_NUMERICHOST; + } + } else if (sockname.sa.sa_family == AF_INET6) { + if ((IN6_ARE_ADDR_EQUAL(&sockname.sa6.sin6_addr, + &in6addr_any)) || + (IN6_IS_ADDR_V4MAPPED(&sockname.sa6.sin6_addr) + && sockname.sa6.sin6_addr.s6_addr[12] == 0 + && sockname.sa6.sin6_addr.s6_addr[13] == 0 + && sockname.sa6.sin6_addr.s6_addr[14] == 0 + && sockname.sa6.sin6_addr.s6_addr[15] == 0)) { + flags |= NI_NUMERICHOST; + } + } + getnameinfo(&sockname.sa, size, host, sizeof(host), + port, sizeof(port), flags); + Tcl_DStringAppendElement(dsPtr, host); + Tcl_DStringAppendElement(dsPtr, port); + } + } + } + if (found) { + if (len) { + return TCL_OK; + } + Tcl_DStringEndSublist(dsPtr); + } else { + if (interp) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't get sockname: %s", Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + } + + if ((len == 0) || HAVE_OPTION("-keepalive")) { + int optlen; + BOOL opt = FALSE; + + if (len == 0) { + sock = statePtr->sockets->fd; + Tcl_DStringAppendElement(dsPtr, "-keepalive"); + } + optlen = sizeof(BOOL); + getsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&opt, &optlen); + Tcl_DStringAppendElement(dsPtr, opt ? "1" : "0"); + if (len > 0) { + return TCL_OK; + } + } + + if ((len == 0) || HAVE_OPTION("-nodelay")) { + int optlen; + BOOL opt = FALSE; + + if (len == 0) { + sock = statePtr->sockets->fd; + Tcl_DStringAppendElement(dsPtr, "-nodelay"); + } + optlen = sizeof(BOOL); + getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&opt, &optlen); + Tcl_DStringAppendElement(dsPtr, opt ? "1" : "0"); + if (len > 0) { + return TCL_OK; + } + } + + if (len > 0) { + return Tcl_BadChannelOption(interp, optionName, + "connecting keepalive nodelay peername sockname"); + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TcpWatchProc -- + * + * Informs the channel driver of the events that the generic channel code + * wishes to receive on this socket. + * + * Results: + * None. + * + * Side effects: + * May cause the notifier to poll if any of the specified conditions are + * already true. + * + *---------------------------------------------------------------------- + */ + +static void +TcpWatchProc( + void *instanceData, /* The socket state. */ + int mask) /* Events of interest; an OR-ed combination of + * TCL_READABLE, TCL_WRITABLE and + * TCL_EXCEPTION. */ +{ + TcpState *statePtr = (TcpState *)instanceData; + + /* + * Update the watch events mask. Only if the socket is not a server + * socket. [Bug 557878] + */ + + if (!statePtr->acceptProc) { + statePtr->watchEvents = 0; + if (GOT_BITS(mask, TCL_READABLE)) { + SET_BITS(statePtr->watchEvents, FD_READ | FD_CLOSE); + } + if (GOT_BITS(mask, TCL_WRITABLE)) { + SET_BITS(statePtr->watchEvents, FD_WRITE | FD_CLOSE); + } + + /* + * If there are any conditions already set, then tell the notifier to + * poll rather than block. + */ + + if (statePtr->readyEvents & statePtr->watchEvents) { + Tcl_Time blockTime = { 0, 0 }; + + Tcl_SetMaxBlockTime(&blockTime); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TcpGetHandleProc -- + * + * Called from Tcl_GetChannelHandle to retrieve OS handles from inside a + * TCP socket based channel. + * + * Results: + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no + * handle for the specified direction. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TcpGetHandleProc( + void *instanceData, /* The socket state. */ + TCL_UNUSED(int) /*direction*/, + void **handlePtr) /* Where to store the handle. */ +{ + TcpState *statePtr = (TcpState *)instanceData; + + *handlePtr = INT2PTR(statePtr->sockets->fd); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TcpConnect -- + * + * This function opens a new socket in client mode. + * + * This might be called in 3 circumstances: + * - By a regular socket command + * - By the event handler to continue an asynchronously connect + * - By a blocking socket function (gets/puts) to terminate the + * connect synchronously + * + * Results: + * TCL_OK, if the socket was successfully connected or an asynchronous + * connection is in progress. If an error occurs, TCL_ERROR is returned + * and an error message is left in interp. + * + * Side effects: + * Opens a socket. + * + * Remarks: + * A single host name may resolve to more than one IP address, e.g. for + * an IPv4/IPv6 dual stack host. For handling asynchronously connecting + * sockets in the background for such hosts, this function can act as a + * coroutine. On the first call, it sets up the control variables for the + * two nested loops over the local and remote addresses. Once the first + * connection attempt is in progress, it sets up itself as a writable + * event handler for that socket, and returns. When the callback occurs, + * control is transferred to the "reenter" label, right after the initial + * return and the loops resume as if they had never been interrupted. + * For synchronously connecting sockets, the loops work the usual way. + * + *---------------------------------------------------------------------- + */ + +static int +TcpConnect( + Tcl_Interp *interp, /* For error reporting; can be NULL. */ + TcpState *statePtr) +{ + DWORD error; + int async_connect = GOT_BITS(statePtr->flags, TCP_ASYNC_CONNECT); + /* We are started with async connect and the + * connect notification was not yet + * received. */ + int async_callback = GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING); + /* We were called by the event procedure and + * continue our loop. */ + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + + if (async_callback) { + goto reenter; + } + + for (statePtr->addr = statePtr->addrlist; statePtr->addr != NULL; + statePtr->addr = statePtr->addr->ai_next) { + for (statePtr->myaddr = statePtr->myaddrlist; + statePtr->myaddr != NULL; + statePtr->myaddr = statePtr->myaddr->ai_next) { + /* + * No need to try combinations of local and remote addresses + * of different families. + */ + + if (statePtr->myaddr->ai_family != statePtr->addr->ai_family) { + continue; + } + + /* + * Close the socket if it is still open from the last unsuccessful + * iteration. + */ + + if (statePtr->sockets->fd != INVALID_SOCKET) { + closesocket(statePtr->sockets->fd); + } + + /* + * Get statePtr lock. + */ + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + + /* + * Reset last error from last try + */ + + statePtr->notifierConnectError = 0; + Tcl_SetErrno(0); + + statePtr->sockets->fd = socket(statePtr->myaddr->ai_family, + SOCK_STREAM, 0); + + /* + * Free list lock. + */ + + SetEvent(tsdPtr->socketListLock); + + /* + * Continue on socket creation error. + */ + + if (statePtr->sockets->fd == INVALID_SOCKET) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + continue; + } + + /* + * Win-NT has a misfeature that sockets are inherited in child + * processes by default. Turn off the inherit bit. + */ + + SetHandleInformation((HANDLE) statePtr->sockets->fd, + HANDLE_FLAG_INHERIT, 0); + + /* + * Set kernel space buffering + */ + + TclSockMinimumBuffers((void *)statePtr->sockets->fd, + TCP_BUFFER_SIZE); + + /* + * Try to bind to a local port. + */ + + if (bind(statePtr->sockets->fd, statePtr->myaddr->ai_addr, + statePtr->myaddr->ai_addrlen) == SOCKET_ERROR) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + continue; + } + + /* + * For asynchronous connect set the socket in nonblocking mode + * and activate connect notification + */ + + if (async_connect) { + TcpState *statePtr2; + int in_socket_list = 0; + + /* + * Get statePtr lock. + */ + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + + /* + * Bugfig for 336441ed59 to not ignore notifications until the + * infoPtr is in the list. + * Check if my statePtr is already in the tsdPtr->socketList + * It is set after this call by TcpThreadActionProc and is set + * on a second round. + * + * If not, we buffer my statePtr in the tsd memory so it is + * not lost by the event procedure + */ + + for (statePtr2 = tsdPtr->socketList; statePtr2 != NULL; + statePtr2 = statePtr2->nextPtr) { + if (statePtr2 == statePtr) { + in_socket_list = 1; + break; + } + } + if (!in_socket_list) { + tsdPtr->pendingTcpState = statePtr; + } + + /* + * Set connect mask to connect events + * + * This is activated by a SOCKET_SELECT message to the + * notifier thread. + */ + + SET_BITS(statePtr->selectEvents, FD_CONNECT); + + /* + * Free list lock. + */ + + SetEvent(tsdPtr->socketListLock); + + /* + * Activate accept notification. + */ + + SendSelectMessage(tsdPtr, SELECT, statePtr); + } + + /* + * Attempt to connect to the remote socket. + */ + + connect(statePtr->sockets->fd, statePtr->addr->ai_addr, + statePtr->addr->ai_addrlen); + + error = WSAGetLastError(); + Tcl_WinConvertError(error); + + if (async_connect && error == WSAEWOULDBLOCK) { + /* + * Asynchronous connect + * + * Remember that we jump back behind this next round + */ + + SET_BITS(statePtr->flags, TCP_ASYNC_PENDING); + return TCL_OK; + + reenter: + /* + * Re-entry point for async connect after connect event or + * blocking operation + * + * Clear the reenter flag + */ + + CLEAR_BITS(statePtr->flags, TCP_ASYNC_PENDING); + + /* + * Get statePtr lock. + */ + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + + /* + * Get signaled connect error. + */ + + Tcl_WinConvertError((DWORD) statePtr->notifierConnectError); + + /* + * Clear eventual connect flag. + */ + + CLEAR_BITS(statePtr->selectEvents, FD_CONNECT); + + /* + * Free list lock. + */ + + SetEvent(tsdPtr->socketListLock); + } + + /* + * Clear the tsd socket list pointer if we did not wait for + * the FD_CONNECT asynchronously + */ + + tsdPtr->pendingTcpState = NULL; + + if (Tcl_GetErrno() == 0) { + goto out; + } + } + } + + out: + /* + * Socket connected or connection failed + */ + + /* + * Async connect terminated + */ + + CLEAR_BITS(statePtr->flags, TCP_ASYNC_CONNECT); + + if (Tcl_GetErrno() == 0) { + /* + * Successfully connected + * + * Set up the select mask for read/write events. + */ + + statePtr->selectEvents = FD_READ | FD_WRITE | FD_CLOSE; + + /* + * Register for interest in events in the select mask. Note that this + * automatically places the socket into non-blocking mode. + */ + + SendSelectMessage(tsdPtr, SELECT, statePtr); + } else { + /* + * Connect failed + * + * For async connect schedule a writable event to report the fail. + */ + + if (async_callback) { + /* + * Set up the select mask for read/write events. + */ + + statePtr->selectEvents = FD_WRITE|FD_READ; + + /* + * Get statePtr lock. + */ + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + + /* + * Signal ready readable and writable events. + */ + + SET_BITS(statePtr->readyEvents, FD_WRITE | FD_READ); + + /* + * Flag error to event routine. + */ + + SET_BITS(statePtr->flags, TCP_ASYNC_FAILED); + + /* + * Save connect error to be reported by 'fconfigure -error'. + */ + + statePtr->connectError = Tcl_GetErrno(); + + /* + * Free list lock. + */ + + SetEvent(tsdPtr->socketListLock); + } + + /* + * Error message on synchronous connect + */ + + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't open socket: %s", Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_OpenTcpClient -- + * + * Opens a TCP client socket and creates a channel around it. + * + * Results: + * The channel or NULL if failed. An error message is returned in the + * interpreter on failure. + * + * Side effects: + * Opens a client socket and creates a new channel. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +Tcl_OpenTcpClient( + Tcl_Interp *interp, /* For error reporting; can be NULL. */ + int port, /* Port number to open. */ + const char *host, /* Host on which to open port. */ + const char *myaddr, /* Client-side address */ + int myport, /* Client-side port */ + int async) /* If nonzero, attempt to do an asynchronous + * connect. Otherwise we do a blocking + * connect. */ +{ + TcpState *statePtr; + const char *errorMsg = NULL; + struct addrinfo *addrlist = NULL, *myaddrlist = NULL; + char channelName[SOCK_CHAN_LENGTH]; + + TclInitSockets(); + + /* + * Do the name lookups for the local and remote addresses. + */ + + if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg) + || !TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, + &errorMsg)) { + if (addrlist != NULL) { + freeaddrinfo(addrlist); + } + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't open socket: %s", errorMsg)); + } + return NULL; + } + + statePtr = NewSocketInfo(INVALID_SOCKET); + statePtr->addrlist = addrlist; + statePtr->myaddrlist = myaddrlist; + if (async) { + SET_BITS(statePtr->flags, TCP_ASYNC_CONNECT); + } + + /* + * Create a new client socket and wrap it in a channel. + */ + if (TcpConnect(interp, statePtr) != TCL_OK) { + TcpCloseProc(statePtr, NULL); + return NULL; + } + + TclWinGenerateChannelName(channelName, "sock", statePtr); + statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + statePtr, (TCL_READABLE | TCL_WRITABLE)); + if (TCL_ERROR == Tcl_SetChannelOption(NULL, statePtr->channel, + "-translation", "auto crlf")) { + Tcl_CloseEx(NULL, statePtr->channel, 0); + return NULL; + } else if (TCL_ERROR == Tcl_SetChannelOption(NULL, statePtr->channel, + "-eofchar", "")) { + Tcl_CloseEx(NULL, statePtr->channel, 0); + return NULL; + } + return statePtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_MakeTcpClientChannel -- + * + * Creates a Tcl_Channel from an existing client TCP socket. + * + * Results: + * The Tcl_Channel wrapped around the preexisting TCP socket. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +Tcl_MakeTcpClientChannel( + void *sock) /* The socket to wrap up into a channel. */ +{ + TclInitSockets(); + + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + + /* + * Set kernel space buffering and non-blocking. + */ + + TclSockMinimumBuffers(sock, TCP_BUFFER_SIZE); + + TcpState *statePtr = NewSocketInfo((SOCKET) sock); + + /* + * Start watching for read/write events on the socket. + */ + + statePtr->selectEvents = FD_READ | FD_CLOSE | FD_WRITE; + SendSelectMessage(tsdPtr, SELECT, statePtr); + + char channelName[SOCK_CHAN_LENGTH]; + TclWinGenerateChannelName(channelName, "sock", statePtr); + statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + statePtr, (TCL_READABLE | TCL_WRITABLE)); + Tcl_SetChannelOption(NULL, statePtr->channel, "-translation", "auto crlf"); + return statePtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_OpenTcpServerEx -- + * + * Opens a TCP server socket and creates a channel around it. + * + * Results: + * The channel or NULL if failed. If an error occurred, an error message + * is left in the interp's result if interp is not NULL. + * + * Side effects: + * Opens a server socket and creates a new channel. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +Tcl_OpenTcpServerEx( + Tcl_Interp *interp, /* For error reporting - may be NULL. */ + const char *service, /* Port number to open. */ + const char *myHost, /* Name of local host. */ + unsigned int flags, /* Flags. */ + int backlog, /* Length of OS listen backlog queue, or -1 + * for default. */ + Tcl_TcpAcceptProc *acceptProc, + /* Callback for accepting connections from new + * clients. */ + void *acceptProcData) /* Data for the callback. */ +{ + SOCKET sock = INVALID_SOCKET; + unsigned short chosenport = 0; + struct addrinfo *addrlist = NULL; + struct addrinfo *addrPtr; /* Socket address to listen on. */ + TcpState *statePtr = NULL; /* The returned value. */ + char channelName[SOCK_CHAN_LENGTH]; + u_long flag = 1; /* Indicates nonblocking mode. */ + const char *errorMsg = NULL; + int optvalue, port; + + TclInitSockets(); + + /* + * Construct the addresses for each end of the socket. + */ + + if (TclSockGetPort(interp, service, "tcp", &port) != TCL_OK) { + errorMsg = "invalid port number"; + goto error; + } + + if (!TclCreateSocketAddress(interp, &addrlist, myHost, port, 1, + &errorMsg)) { + goto error; + } + + for (addrPtr = addrlist; addrPtr != NULL; addrPtr = addrPtr->ai_next) { + sock = socket(addrPtr->ai_family, addrPtr->ai_socktype, + addrPtr->ai_protocol); + if (sock == INVALID_SOCKET) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + continue; + } + + /* + * Win-NT has a misfeature that sockets are inherited in child + * processes by default. Turn off the inherit bit. + */ + + SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0); + + /* + * Set kernel space buffering + */ + + TclSockMinimumBuffers((void *)sock, TCP_BUFFER_SIZE); + + /* + * Make sure we use the same port when opening two server sockets + * for IPv4 and IPv6. + * + * As sockaddr_in6 uses the same offset and size for the port + * member as sockaddr_in, we can handle both through the IPv4 API. + */ + + if (port == 0 && chosenport != 0) { + ((struct sockaddr_in *) addrPtr->ai_addr)->sin_port = + htons(chosenport); + } + + /* + * The SO_REUSEADDR option on Windows behaves like SO_REUSEPORT on + * unix systems. + */ + + if (GOT_BITS(flags, TCL_TCPSERVER_REUSEPORT)) { + optvalue = 1; + (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (char *) &optvalue, sizeof(optvalue)); + } + + /* + * Bind to the specified port. + * + * Bind should not be affected by the socket having already been + * set into nonblocking mode. If there is trouble, this is one + * place to look for bugs. + */ + + if (bind(sock, addrPtr->ai_addr, + addrPtr->ai_addrlen) == SOCKET_ERROR) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + closesocket(sock); + sock = INVALID_SOCKET; /* Bug [40b1814b93] */ + continue; + } + if (port == 0 && chosenport == 0) { + address sockname; + socklen_t namelen = sizeof(sockname); + + /* + * Synchronize port numbers when binding to port 0 of multiple + * addresses. + */ + + if (getsockname(sock, &sockname.sa, &namelen) >= 0) { + chosenport = ntohs(sockname.sa4.sin_port); + } + } + + /* + * Set the maximum number of pending connect requests to the max + * value allowed on each platform (Win32 and Win32s may be + * different, and there may be differences between TCP/IP stacks). + */ + + if (backlog < 0) { + backlog = SOMAXCONN; + } + if (listen(sock, backlog) == SOCKET_ERROR) { + Tcl_WinConvertError((DWORD) WSAGetLastError()); + closesocket(sock); + sock = INVALID_SOCKET; /* Bug [40b1814b93] */ + continue; + } + + if (statePtr == NULL) { + /* + * Add this socket to the global list of sockets. + */ + + statePtr = NewSocketInfo(sock); + } else { + AddSocketInfoFd(statePtr, sock); + } + } + + error: + if (addrlist != NULL) { + freeaddrinfo(addrlist); + } + + if (statePtr != NULL) { + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + + statePtr->acceptProc = acceptProc; + statePtr->acceptProcData = acceptProcData; + TclWinGenerateChannelName(channelName, "sock", statePtr); + statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + statePtr, 0); + /* + * Set up the select mask for connection request events. + */ + + statePtr->selectEvents = FD_ACCEPT; + + /* + * Register for interest in events in the select mask. Note that this + * automatically places the socket into non-blocking mode. + */ + + ioctlsocket(sock, (long) FIONBIO, &flag); + SendSelectMessage(tsdPtr, SELECT, statePtr); + if (Tcl_SetChannelOption(interp, statePtr->channel, "-eofchar", "") + == TCL_ERROR) { + Tcl_CloseEx(NULL, statePtr->channel, 0); + return NULL; + } + return statePtr->channel; + } + + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't open socket: %s", + (errorMsg ? errorMsg : Tcl_PosixError(interp)))); + } + + if (sock != INVALID_SOCKET) { + closesocket(sock); + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * TcpAccept -- + * Accept a TCP socket connection. This is called by the event loop. + * + * Results: + * None. + * + * Side effects: + * Creates a new connection socket. Calls the registered callback for the + * connection acceptance mechanism. + * + *---------------------------------------------------------------------- + */ + +static void +TcpAccept( + TcpFdList *fds, /* Server socket that accepted newSocket. */ + SOCKET newSocket, /* Newly accepted socket. */ + address addr) /* Address of new socket. */ +{ + TcpState *newInfoPtr; + TcpState *statePtr = fds->statePtr; + int len = sizeof(addr); + char channelName[SOCK_CHAN_LENGTH]; + char host[NI_MAXHOST], port[NI_MAXSERV]; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + + /* + * Win-NT has a misfeature that sockets are inherited in child processes + * by default. Turn off the inherit bit. + */ + + SetHandleInformation((HANDLE) newSocket, HANDLE_FLAG_INHERIT, 0); + + /* + * Add this socket to the global list of sockets. + */ + + newInfoPtr = NewSocketInfo(newSocket); + + /* + * Select on read/write events and create the channel. + */ + + newInfoPtr->selectEvents = (FD_READ | FD_WRITE | FD_CLOSE); + SendSelectMessage(tsdPtr, SELECT, newInfoPtr); + + TclWinGenerateChannelName(channelName, "sock", newInfoPtr); + newInfoPtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, + newInfoPtr, (TCL_READABLE | TCL_WRITABLE)); + if (Tcl_SetChannelOption(NULL, newInfoPtr->channel, "-translation", + "auto crlf") == TCL_ERROR) { + Tcl_CloseEx(NULL, newInfoPtr->channel, 0); + return; + } + if (Tcl_SetChannelOption(NULL, newInfoPtr->channel, "-eofchar", "") + == TCL_ERROR) { + Tcl_CloseEx(NULL, newInfoPtr->channel, 0); + return; + } + + /* + * Invoke the accept callback function. + */ + + if (statePtr->acceptProc != NULL) { + getnameinfo(&(addr.sa), len, host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST|NI_NUMERICSERV); + statePtr->acceptProc(statePtr->acceptProcData, newInfoPtr->channel, + host, atoi(port)); + } +} + +/* + *---------------------------------------------------------------------- + * + * InitSocketWindowClass -- + * + * Registers the event window class for the socket notifier code. + * Caller must not hold socket mutex lock. + * + * Results: + * None. + * + * Side effects: + * Register a new window class. + * + *---------------------------------------------------------------------- + */ + +static void +InitSocketWindowClass(void) +{ + if (initialized) { + return; + } + Tcl_MutexLock(&socketMutex); + if (!initialized) { + initialized = 1; + TclCreateLateExitHandler(SocketExitHandler, NULL); + + /* + * Create the async notification window with a new class. We must + * create a new class to avoid a Windows 95 bug that causes us to get + * the wrong message number for socket events if the message window is + * a subclass of a static control. + */ + + windowClass.style = 0; + windowClass.cbClsExtra = 0; + windowClass.cbWndExtra = 0; + windowClass.hInstance = (HINSTANCE)TclWinGetTclInstance(); + windowClass.hbrBackground = NULL; + windowClass.lpszMenuName = NULL; + windowClass.lpszClassName = className; + windowClass.lpfnWndProc = SocketProc; + windowClass.hIcon = NULL; + windowClass.hCursor = NULL; + + if (!RegisterClassW(&windowClass)) { + Tcl_WinConvertError(GetLastError()); + goto initFailure; + } + } + Tcl_MutexUnlock(&socketMutex); + return; + + initFailure: + Tcl_MutexUnlock(&socketMutex); /* Probably pointless before panicing */ + Tcl_Panic("InitSockets failed"); +} + +/* + *---------------------------------------------------------------------- + * + * SocketExitHandler -- + * + * Callback invoked during exit clean up to delete the socket + * communication window. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +SocketExitHandler( + TCL_UNUSED(void *)) +{ + Tcl_MutexLock(&socketMutex); + + /* + * Make sure the socket event handling window is cleaned-up for, at + * most, this thread. + */ + + TclpFinalizeSockets(); + UnregisterClassW(className, (HINSTANCE)TclWinGetTclInstance()); + initialized = 0; + Tcl_MutexUnlock(&socketMutex); +} + +/* + *---------------------------------------------------------------------- + * + * SocketSetupProc -- + * + * This function is invoked before Tcl_DoOneEvent blocks waiting for an + * event. + * + * Results: + * None. + * + * Side effects: + * Adjusts the block time if needed. + * + *---------------------------------------------------------------------- + */ + +void +SocketSetupProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + TcpState *statePtr; + Tcl_Time blockTime = { 0, 0 }; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!GOT_BITS(flags, TCL_FILE_EVENTS)) { + return; + } + + /* + * Check to see if there is a ready socket. If so, poll. + */ + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + for (statePtr = tsdPtr->socketList; statePtr != NULL; + statePtr = statePtr->nextPtr) { + if (GOT_BITS(statePtr->readyEvents, + statePtr->watchEvents | FD_CONNECT | FD_ACCEPT)) { + Tcl_SetMaxBlockTime(&blockTime); + break; + } + } + SetEvent(tsdPtr->socketListLock); +} + +/* + *---------------------------------------------------------------------- + * + * SocketCheckProc -- + * + * This function is called by Tcl_DoOneEvent to check the socket event + * source for events. + * + * Results: + * None. + * + * Side effects: + * May queue an event. + * + *---------------------------------------------------------------------- + */ + +static void +SocketCheckProc( + TCL_UNUSED(void *), + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + TcpState *statePtr; + SocketEvent *evPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!GOT_BITS(flags, TCL_FILE_EVENTS)) { + return; + } + + /* + * Queue events for any ready sockets that don't already have events + * queued (caused by persistent states that won't generate WinSock + * events). + */ + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + for (statePtr = tsdPtr->socketList; statePtr != NULL; + statePtr = statePtr->nextPtr) { + if (GOT_BITS(statePtr->readyEvents, + statePtr->watchEvents | FD_CONNECT | FD_ACCEPT) + && !GOT_BITS(statePtr->flags, SOCKET_PENDING)) { + SET_BITS(statePtr->flags, SOCKET_PENDING); + evPtr = (SocketEvent *)Tcl_Alloc(sizeof(SocketEvent)); + evPtr->header.proc = SocketEventProc; + evPtr->socket = statePtr->sockets->fd; + Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); + } + } + SetEvent(tsdPtr->socketListLock); +} + +/* + *---------------------------------------------------------------------- + * + * SocketEventProc -- + * + * This function is called by Tcl_ServiceEvent when a socket event + * reaches the front of the event queue. This function is responsible for + * notifying the generic channel code. + * + * Results: + * Returns 1 if the event was handled, meaning it should be removed from + * the queue. Returns 0 if the event was not handled, meaning it should + * stay on the queue. The only time the event isn't handled is if the + * TCL_FILE_EVENTS flag bit isn't set. + * + * Side effects: + * Whatever the channel callback functions do. + * + *---------------------------------------------------------------------- + */ + +static int +SocketEventProc( + Tcl_Event *evPtr, /* Event to service. */ + int flags) /* Flags that indicate what events to handle, + * such as TCL_FILE_EVENTS. */ +{ + TcpState *statePtr; + SocketEvent *eventPtr = (SocketEvent *) evPtr; + int mask = 0, events; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + TcpFdList *fds; + SOCKET newSocket; + address addr; + int len; + + if (!GOT_BITS(flags, TCL_FILE_EVENTS)) { + return 0; + } + + /* + * Find the specified socket on the socket list. + */ + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + for (statePtr = tsdPtr->socketList; statePtr != NULL; + statePtr = statePtr->nextPtr) { + if (statePtr->sockets->fd == eventPtr->socket) { + break; + } + } + + /* + * Discard events that have gone stale. + */ + + if (!statePtr) { + SetEvent(tsdPtr->socketListLock); + return 1; + } + + /* + * Clear flag that (this) event is pending + */ + + CLEAR_BITS(statePtr->flags, SOCKET_PENDING); + + /* + * Continue async connect if pending and ready + */ + + if (GOT_BITS(statePtr->readyEvents, FD_CONNECT)) { + if (GOT_BITS(statePtr->flags, TCP_ASYNC_PENDING)) { + /* + * Do one step and save eventual connect error + */ + + SetEvent(tsdPtr->socketListLock); + WaitForConnect(statePtr,NULL); + } else { + /* + * No async connect reenter pending. Just clear event. + */ + + CLEAR_BITS(statePtr->readyEvents, FD_CONNECT); + SetEvent(tsdPtr->socketListLock); + } + return 1; + } + + /* + * Handle connection requests directly. + */ + + if (GOT_BITS(statePtr->readyEvents, FD_ACCEPT)) { + for (fds = statePtr->sockets; fds != NULL; fds = fds->next) { + /* + * Accept the incoming connection request. + */ + + len = sizeof(address); + newSocket = accept(fds->fd, &(addr.sa), &len); + + /* + * On Tcl server sockets with multiple OS fds we loop over the fds + * trying an accept() on each, so we expect INVALID_SOCKET. There + * are also other network stack conditions that can result in + * FD_ACCEPT but a subsequent failure on accept() by the time we + * get around to it. + * + * Access to sockets (acceptEventCount, readyEvents) in socketList + * is still protected by the lock (prevents reintroduction of + * SF Tcl Bug 3056775. + */ + + if (newSocket == INVALID_SOCKET) { + /* int err = WSAGetLastError(); */ + continue; + } + + /* + * It is possible that more than one FD_ACCEPT has been sent, so + * an extra count must be kept. Decrement the count, and reset the + * readyEvent bit if the count is no longer > 0. + */ + + statePtr->acceptEventCount--; + + if (statePtr->acceptEventCount <= 0) { + CLEAR_BITS(statePtr->readyEvents, FD_ACCEPT); + } + + SetEvent(tsdPtr->socketListLock); + + /* + * Caution: TcpAccept() has the side-effect of evaluating the + * server accept script (via AcceptCallbackProc() in tclIOCmd.c), + * which can close the server socket and invalidate statePtr and + * fds. If TcpAccept() accepts a socket we must return immediately + * and let SocketCheckProc queue additional FD_ACCEPT events. + */ + + TcpAccept(fds, newSocket, addr); + return 1; + } + + /* + * Loop terminated with no sockets accepted; clear the ready mask so + * we can detect the next connection request. Note that connection + * requests are level triggered, so if there is a request already + * pending, a new event will be generated. + */ + + statePtr->acceptEventCount = 0; + CLEAR_BITS(statePtr->readyEvents, FD_ACCEPT); + + SetEvent(tsdPtr->socketListLock); + return 1; + } + + SetEvent(tsdPtr->socketListLock); + + /* + * Mask off unwanted events and compute the read/write mask so we can + * notify the channel. + */ + + events = statePtr->readyEvents & statePtr->watchEvents; + + if (GOT_BITS(events, FD_CLOSE)) { + /* + * If the socket was closed and the channel is still interested in + * read events, then we need to ensure that we keep polling for this + * event until someone does something with the channel. Note that we + * do this before calling Tcl_NotifyChannel so we don't have to watch + * out for the channel being deleted out from under us. This may cause + * a redundant trip through the event loop, but it's simpler than + * trying to do unwind protection. + */ + + Tcl_Time blockTime = { 0, 0 }; + + Tcl_SetMaxBlockTime(&blockTime); + SET_BITS(mask, TCL_READABLE | TCL_WRITABLE); + } else if (GOT_BITS(events, FD_READ)) { + /* + * Throw the readable event if an async connect failed. + */ + + if (GOT_BITS(statePtr->flags, TCP_ASYNC_FAILED)) { + SET_BITS(mask, TCL_READABLE); + } else { + fd_set readFds; + struct timeval timeout; + + /* + * We must check to see if data is really available, since someone + * could have consumed the data in the meantime. Turn off async + * notification so select will work correctly. If the socket is + * still readable, notify the channel driver, otherwise reset the + * async select handler and keep waiting. + */ + + SendSelectMessage(tsdPtr, UNSELECT, statePtr); + + FD_ZERO(&readFds); + FD_SET(statePtr->sockets->fd, &readFds); + timeout.tv_usec = 0; + timeout.tv_sec = 0; + + if (select(0, &readFds, NULL, NULL, &timeout) != 0) { + SET_BITS(mask, TCL_READABLE); + } else { + CLEAR_BITS(statePtr->readyEvents, FD_READ); + SendSelectMessage(tsdPtr, SELECT, statePtr); + } + } + } + + /* + * writable event + */ + + if (GOT_BITS(events, FD_WRITE)) { + SET_BITS(mask, TCL_WRITABLE); + } + + /* + * Call registered event procedures + */ + + if (mask) { + Tcl_NotifyChannel(statePtr->channel, mask); + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * AddSocketInfoFd -- + * + * This function adds a SOCKET file descriptor to the 'sockets' linked + * list of a TcpState structure. + * + * Results: + * None. + * + * Side effects: + * None, except for allocation of memory. + * + *---------------------------------------------------------------------- + */ + +static void +AddSocketInfoFd( + TcpState *statePtr, + SOCKET socket) +{ + TcpFdList *fds = statePtr->sockets; + + if (fds == NULL) { + /* + * Add the first FD. + */ + + statePtr->sockets = (TcpFdList *)Tcl_Alloc(sizeof(TcpFdList)); + fds = statePtr->sockets; + } else { + /* + * Find end of list and append FD. + */ + + while (fds->next != NULL) { + fds = fds->next; + } + + fds->next = (TcpFdList *)Tcl_Alloc(sizeof(TcpFdList)); + fds = fds->next; + } + + /* + * Populate new FD. + */ + + fds->fd = socket; + fds->statePtr = statePtr; + fds->next = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * NewSocketInfo -- + * + * This function allocates and initializes a new TcpState structure. + * + * Results: + * Returns a newly allocated TcpState. + * + * Side effects: + * None, except for allocation of memory. + * + *---------------------------------------------------------------------- + */ + +static TcpState * +NewSocketInfo( + SOCKET socket) +{ + TcpState *statePtr = (TcpState *)Tcl_Alloc(sizeof(TcpState)); + + memset(statePtr, 0, sizeof(TcpState)); + + /* + * TIP #218. Removed the code inserting the new structure into the global + * list. This is now handled in the thread action callbacks, and only + * there. + */ + + AddSocketInfoFd(statePtr, socket); + + return statePtr; +} + +/* + *---------------------------------------------------------------------- + * + * WaitForSocketEvent -- + * + * Waits until one of the specified events occurs on a socket. + * For event FD_CONNECT use WaitForConnect. + * + * Results: + * Returns 1 on success or 0 on failure, with an error code in + * errorCodePtr. + * + * Side effects: + * Processes socket events off the system queue. + * + *---------------------------------------------------------------------- + */ + +static int +WaitForSocketEvent( + TcpState *statePtr, /* Information about this socket. */ + int events, /* Events to look for. May be one of + * FD_READ or FD_WRITE. */ + int *errorCodePtr) /* Where to store errors? */ +{ + int result = 1; + int oldMode; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + TclThreadDataKeyGet(&dataKey); + + /* + * Be sure to disable event servicing so we are truly modal. + */ + + oldMode = Tcl_SetServiceMode(TCL_SERVICE_NONE); + + /* + * Reset WSAAsyncSelect so we have a fresh set of events pending. + */ + + SendSelectMessage(tsdPtr, UNSELECT, statePtr); + SendSelectMessage(tsdPtr, SELECT, statePtr); + + while (1) { + int event_found; + + /* + * Get statePtr lock. + */ + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + + /* + * Check if event occurred. + */ + + event_found = GOT_BITS(statePtr->readyEvents, events); + + /* + * Free list lock. + */ + + SetEvent(tsdPtr->socketListLock); + + /* + * Exit loop if event occurred. + */ + + if (event_found) { + break; + } + + /* + * Exit loop if event did not occur but this is a non-blocking channel + */ + + if (statePtr->flags & TCP_NONBLOCKING) { + *errorCodePtr = EWOULDBLOCK; + result = 0; + break; + } + + /* + * Wait until something happens. + */ + + WaitForSingleObject(tsdPtr->readyEvent, INFINITE); + } + + (void) Tcl_SetServiceMode(oldMode); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * SocketThread -- + * + * Helper thread used to manage the socket event handling window. + * + * Results: + * 1 if unable to create socket event window, 0 otherwise. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static DWORD WINAPI +SocketThread( + LPVOID arg) +{ + MSG msg; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *)arg; + + /* + * Create a dummy window receiving socket events. + */ + + tsdPtr->hwnd = CreateWindowW(className, className, WS_TILED, 0, 0, 0, 0, + NULL, NULL, windowClass.hInstance, arg); + + /* + * Signalize thread creator that we are done creating the window. + */ + + SetEvent(tsdPtr->readyEvent); + + /* + * If unable to create the window, exit this thread immediately. + */ + + if (tsdPtr->hwnd == NULL) { + return 1; + } + + /* + * Process all messages on the socket window until WM_QUIT. This threads + * exits only when instructed to do so by the call to + * PostMessageW(SOCKET_TERMINATE) in TclpFinalizeSockets(). + */ + + while (GetMessageW(&msg, NULL, 0, 0) > 0) { + DispatchMessageW(&msg); + } + + /* + * This releases waiters on thread exit in TclpFinalizeSockets() + */ + + SetEvent(tsdPtr->readyEvent); + + return msg.wParam; +} + +/* + *---------------------------------------------------------------------- + * + * SocketProc -- + * + * This function is called when WSAAsyncSelect has been used to register + * interest in a socket event, and the event has occurred. + * + * Results: + * 0 on success. + * + * Side effects: + * The flags for the given socket are updated to reflect the event that + * occurred. + * + *---------------------------------------------------------------------- + */ + +static LRESULT CALLBACK +SocketProc( + HWND hwnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + int event, error; + SOCKET socket; + TcpState *statePtr; + int info_found = 0; + TcpFdList *fds = NULL; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) +#ifdef _WIN64 + GetWindowLongPtrW(hwnd, GWLP_USERDATA); +#else + GetWindowLongW(hwnd, GWL_USERDATA); +#endif + + switch (message) { + default: + return DefWindowProcW(hwnd, message, wParam, lParam); + break; + + case WM_CREATE: + /* + * Store the initial tsdPtr, it's from a different thread, so it's not + * directly accessible, but needed. + */ + +#ifdef _WIN64 + SetWindowLongPtrW(hwnd, GWLP_USERDATA, + (LONG_PTR) ((LPCREATESTRUCT)lParam)->lpCreateParams); +#else + SetWindowLongW(hwnd, GWL_USERDATA, + (LONG) ((LPCREATESTRUCT)lParam)->lpCreateParams); +#endif + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + case SOCKET_MESSAGE: + event = WSAGETSELECTEVENT(lParam); + error = WSAGETSELECTERROR(lParam); + socket = (SOCKET) wParam; + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + + /* + * Find the specified socket on the socket list and update its + * eventState flag. + */ + + for (statePtr = tsdPtr->socketList; statePtr != NULL; + statePtr = statePtr->nextPtr) { + if (FindFDInList(statePtr, socket)) { + info_found = 1; + break; + } + } + + /* + * Check if there is a pending info structure not jet in the list. + */ + + if (!info_found + && tsdPtr->pendingTcpState != NULL + && FindFDInList(tsdPtr->pendingTcpState, socket)) { + statePtr = tsdPtr->pendingTcpState; + info_found = 1; + } + if (info_found) { + /* + * Update the socket state. + * + * A count of FD_ACCEPTS is stored, so if an FD_CLOSE event + * happens, then clear the FD_ACCEPT count. Otherwise, increment + * the count if the current event is an FD_ACCEPT. + */ + + if (GOT_BITS(event, FD_CLOSE)) { + statePtr->acceptEventCount = 0; + CLEAR_BITS(statePtr->readyEvents, FD_WRITE | FD_ACCEPT); + } else if (GOT_BITS(event, FD_ACCEPT)) { + statePtr->acceptEventCount++; + } + + if (GOT_BITS(event, FD_CONNECT)) { + /* + * Remember any error that occurred so we can report + * connection failures. + */ + + if (error != ERROR_SUCCESS) { + statePtr->notifierConnectError = error; + } + } + + /* + * Inform main thread about signaled events + */ + + SET_BITS(statePtr->readyEvents, event); + + /* + * Wake up the Main Thread. + */ + + SetEvent(tsdPtr->readyEvent); + Tcl_ThreadAlert(tsdPtr->threadId); + } + SetEvent(tsdPtr->socketListLock); + break; + + case SOCKET_SELECT: + statePtr = (TcpState *) lParam; + for (fds = statePtr->sockets; fds != NULL; fds = fds->next) { + if (wParam == SELECT) { + WSAAsyncSelect(fds->fd, hwnd, + SOCKET_MESSAGE, statePtr->selectEvents); + } else { + /* + * Clear the selection mask + */ + + WSAAsyncSelect(fds->fd, hwnd, 0, 0); + } + } + break; + + case SOCKET_TERMINATE: + DestroyWindow(hwnd); + break; + } + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FindFDInList -- + * + * Return true, if the given file descriptor is contained in the + * file descriptor list. + * + * Results: + * true if found. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static int +FindFDInList( + TcpState *statePtr, + SOCKET socket) +{ + TcpFdList *fds; + for (fds = statePtr->sockets; fds != NULL; fds = fds->next) { + if (fds->fd == socket) { + return 1; + } + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * TcpThreadActionProc -- + * + * Insert or remove any thread local refs to this channel. + * + * Results: + * None. + * + * Side effects: + * Changes thread local list of valid channels. + * + *---------------------------------------------------------------------- + */ + +static void +TcpThreadActionProc( + void *instanceData, + int action) +{ + ThreadSpecificData *tsdPtr; + TcpState *statePtr = (TcpState *)instanceData; + int notifyCmd; + + if (action == TCL_CHANNEL_THREAD_INSERT) { + /* + * Ensure that socket subsystem is initialized in this thread, or else + * sockets will not work. + */ + + TclInitSockets(); + + tsdPtr = TCL_TSD_INIT(&dataKey); + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + statePtr->nextPtr = tsdPtr->socketList; + tsdPtr->socketList = statePtr; + + if (statePtr == tsdPtr->pendingTcpState) { + tsdPtr->pendingTcpState = NULL; + } + + SetEvent(tsdPtr->socketListLock); + + notifyCmd = SELECT; + } else { + TcpState **nextPtrPtr; + int removed = 0; + + tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * TIP #218, Bugfix: All access to socketList has to be protected by + * the lock. + */ + + WaitForSingleObject(tsdPtr->socketListLock, INFINITE); + for (nextPtrPtr = &(tsdPtr->socketList); (*nextPtrPtr) != NULL; + nextPtrPtr = &((*nextPtrPtr)->nextPtr)) { + if ((*nextPtrPtr) == statePtr) { + (*nextPtrPtr) = statePtr->nextPtr; + removed = 1; + break; + } + } + SetEvent(tsdPtr->socketListLock); + + /* + * This could happen if the channel was created in one thread and then + * moved to another without updating the thread local data in each + * thread. + */ + + if (!removed) { + Tcl_Panic("file info ptr not on thread channel list"); + } + + notifyCmd = UNSELECT; + } + + /* + * Ensure that, or stop, notifications for the socket occur in this + * thread. + */ + + SendSelectMessage(tsdPtr, notifyCmd, statePtr); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + */ diff --git a/vendor/tcl/win/tclWinTest.c b/vendor/tcl/win/tclWinTest.c new file mode 100644 index 00000000..005fb372 --- /dev/null +++ b/vendor/tcl/win/tclWinTest.c @@ -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 +#include + +/* + * 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 + * + * 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, ""); + 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: + */ diff --git a/vendor/tcl/win/tclWinThrd.c b/vendor/tcl/win/tclWinThrd.c new file mode 100644 index 00000000..5b4bc9eb --- /dev/null +++ b/vendor/tcl/win/tclWinThrd.c @@ -0,0 +1,1156 @@ +/* + * tclWinThread.c -- + * + * This file implements the Windows-specific thread operations. + * + * Copyright © 1998 Sun Microsystems, Inc. + * Copyright © 1999 Scriptics Corporation + * Copyright © 2008 George Peter Staplin + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#include "tclWinInt.h" + +/* Workaround for mingw versions which don't provide this in float.h */ +#ifndef _MCW_EM +# define _MCW_EM 0x0008001F /* Error masks */ +# define _MCW_RC 0x00000300 /* Rounding */ +# define _MCW_PC 0x00030000 /* Precision */ +_CRTIMP unsigned int __cdecl _controlfp (unsigned int unNew, unsigned int unMask); +#endif + +/* + * This is the global lock used to serialize access to other serialization + * data structures. + */ + +static CRITICAL_SECTION globalLock; +static int initialized = 0; + +/* + * This is the global lock used to serialize initialization and finalization + * of Tcl as a whole. + */ + +static CRITICAL_SECTION initLock; + +/* + * allocLock is used by Tcl's version of malloc for synchronization. For + * obvious reasons, cannot use any dynamically allocated storage. + */ + +#if TCL_THREADS + +/* + * Although CRITICAL_SECTIONs can be nested, we need to keep track + * of their lock counts for condition variables. + */ + +typedef struct WMutex { + CRITICAL_SECTION crit; + volatile DWORD thread; + int counter; +} WMutex; + +static struct WMutex allocLock; +static WMutex *allocLockPtr = &allocLock; +static int allocOnce = 0; + +#endif /* TCL_THREADS */ + +/* + * The joinLock serializes Create- and ExitThread. This is necessary to + * prevent a race where a new joinable thread exits before the creating thread + * had the time to create the necessary data structures in the emulation + * layer. + */ + +static CRITICAL_SECTION joinLock; + +/* + * Condition variables are implemented with a combination of a per-thread + * Windows Event and a per-condition waiting queue. The idea is that each + * thread has its own Event that it waits on when it is doing a ConditionWait; + * it uses the same event for all condition variables because it only waits on + * one at a time. Each condition variable has a queue of waiting threads, and + * a mutex used to serialize access to this queue. + * + * Special thanks to David Nichols and Jim Davidson for advice on the + * Condition Variable implementation. + */ + +/* + * The per-thread event and queue pointers. + */ + +#if TCL_THREADS + +typedef struct ThreadSpecificData { + HANDLE condEvent; /* Per-thread condition event */ + struct ThreadSpecificData *nextPtr; /* Queue pointers */ + struct ThreadSpecificData *prevPtr; + int flags; /* See ThreadStateFlags below */ +} ThreadSpecificData; +static Tcl_ThreadDataKey dataKey; + +#endif /* TCL_THREADS */ + +/* + * State bits for the thread. + */ +enum ThreadStateFlags { + WIN_THREAD_UNINIT = 0x0, /* Uninitialized. Must be zero because of the + * way ThreadSpecificData is created. */ + WIN_THREAD_RUNNING = 0x1, /* Running, not waiting. */ + WIN_THREAD_BLOCKED = 0x2 /* Waiting, or trying to wait. */ +}; + +/* + * The per condition queue pointers and the Mutex used to serialize access to + * the queue. + */ + +typedef struct { + CRITICAL_SECTION condLock; /* Lock to serialize queuing on the + * condition. */ + ThreadSpecificData *firstPtr; /* Queue pointers */ + ThreadSpecificData *lastPtr; +} WinCondition; + +/* + * Additions by AOL for specialized thread memory allocator. + */ + +#ifdef USE_THREAD_ALLOC +static DWORD tlsKey; + +typedef struct { + Tcl_Mutex tlock; + WMutex wm; +} allocMutex; +#endif /* USE_THREAD_ALLOC */ + +static void WMutexInit(WMutex *); +static void WMutexDestroy(WMutex *); + +/* + * The per thread data passed from TclpThreadCreate + * to TclWinThreadStart. + */ + +typedef struct { + LPTHREAD_START_ROUTINE lpStartAddress; + /* Original startup routine */ + LPVOID lpParameter; /* Original startup data */ + unsigned int fpControl; /* Floating point control word from the + * main thread */ +} WinThread; + +/* + *---------------------------------------------------------------------- + * + * TclWinThreadStart -- + * + * This procedure is the entry point for all new threads created + * by Tcl on Windows. + * + * Results: + * Various, depending on the result of the wrapped thread start + * routine. + * + * Side effects: + * Arbitrary, since user code is executed. + * + *---------------------------------------------------------------------- + */ + +static DWORD WINAPI +TclWinThreadStart( + LPVOID lpParameter) /* The WinThread structure pointer passed + * from TclpThreadCreate */ +{ + WinThread *winThreadPtr = (WinThread *) lpParameter; + LPTHREAD_START_ROUTINE lpOrigStartAddress; + LPVOID lpOrigParameter; + + if (!winThreadPtr) { + return TCL_ERROR; + } + + _controlfp(winThreadPtr->fpControl, _MCW_EM | _MCW_RC | 0x03000000 /* _MCW_DN */ +#if !defined(_WIN64) + | _MCW_PC +#endif + ); + + lpOrigStartAddress = winThreadPtr->lpStartAddress; + lpOrigParameter = winThreadPtr->lpParameter; + + Tcl_Free(winThreadPtr); + return lpOrigStartAddress(lpOrigParameter); +} + +/* + *---------------------------------------------------------------------- + * + * TclpThreadCreate -- + * + * This procedure creates a new thread. + * + * Results: + * TCL_OK if the thread could be created. The thread ID is returned in a + * parameter. + * + * Side effects: + * A new thread is created. + * + *---------------------------------------------------------------------- + */ + +int +TclpThreadCreate( + Tcl_ThreadId *idPtr, /* Return, the ID of the thread. */ + Tcl_ThreadCreateProc *proc, /* Main() function of the thread. */ + void *clientData, /* The one argument to Main(). */ + size_t stackSize, /* Size of stack for the new thread. */ + int flags) /* Flags controlling behaviour of the new + * thread. */ +{ + WinThread *winThreadPtr; /* Per-thread startup info */ + HANDLE tHandle; + + winThreadPtr = (WinThread *)Tcl_Alloc(sizeof(WinThread)); + winThreadPtr->lpStartAddress = (LPTHREAD_START_ROUTINE) proc; + winThreadPtr->lpParameter = clientData; + winThreadPtr->fpControl = _controlfp(0, 0); + + EnterCriticalSection(&joinLock); + + *idPtr = 0; /* must initialize as Tcl_Thread is a pointer and + * on WIN64 sizeof void* != sizeof unsigned */ + +#if defined(_MSC_VER) || defined(__MSVCRT__) + tHandle = (HANDLE) _beginthreadex(NULL, (unsigned)stackSize, + (Tcl_ThreadCreateProc*) TclWinThreadStart, winThreadPtr, + 0, (unsigned *)idPtr); +#else + tHandle = CreateThread(NULL, (DWORD)stackSize, + TclWinThreadStart, winThreadPtr, 0, (LPDWORD)idPtr); +#endif + + if (tHandle == NULL) { + LeaveCriticalSection(&joinLock); + return TCL_ERROR; + } else { + if (flags & TCL_THREAD_JOINABLE) { + TclRememberJoinableThread(*idPtr); + } + + /* + * The only purpose of this is to decrement the reference count so the + * OS resources will be reacquired when the thread closes. + */ + + CloseHandle(tHandle); + LeaveCriticalSection(&joinLock); + return TCL_OK; + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_JoinThread -- + * + * This procedure waits upon the exit of the specified thread. + * + * Results: + * TCL_OK if the wait was successful, TCL_ERROR else. + * + * Side effects: + * The result area is set to the exit code of the thread we + * waited upon. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_JoinThread( + Tcl_ThreadId threadId, /* Id of the thread to wait upon */ + int *result) /* Reference to the storage the result of the + * thread we wait upon will be written into. */ +{ + return TclJoinThread(threadId, result); +} + +/* + *---------------------------------------------------------------------- + * + * TclpThreadExit -- + * + * This procedure terminates the current thread. + * + * Results: + * None. + * + * Side effects: + * This procedure terminates the current thread. + * + *---------------------------------------------------------------------- + */ + +TCL_NORETURN void +TclpThreadExit( + int status) +{ + EnterCriticalSection(&joinLock); + TclSignalExitThread(Tcl_GetCurrentThread(), status); + LeaveCriticalSection(&joinLock); + +#if defined(_MSC_VER) || defined(__MSVCRT__) + _endthreadex((unsigned) status); +#else + ExitThread((DWORD) status); +#endif +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_GetCurrentThread -- + * + * This procedure returns the ID of the currently running thread. + * + * Results: + * A thread ID. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_ThreadId +Tcl_GetCurrentThread(void) +{ + return (Tcl_ThreadId)INT2PTR(GetCurrentThreadId()); +} + +/* + *---------------------------------------------------------------------- + * + * TclpInitLock + * + * This procedure is used to grab a lock that serializes initialization + * and finalization of Tcl. On some platforms this may also initialize + * the mutex used to serialize creation of more mutexes and thread local + * storage keys. + * + * Results: + * None. + * + * Side effects: + * Acquire the initialization mutex. + * + *---------------------------------------------------------------------- + */ + +void +TclpInitLock(void) +{ + if (!initialized) { + /* + * There is a fundamental race here that is solved by creating the + * first Tcl interpreter in a single threaded environment. Once the + * interpreter has been created, it is safe to create more threads + * that create interpreters in parallel. + */ + + initialized = 1; + InitializeCriticalSection(&joinLock); + InitializeCriticalSection(&initLock); + InitializeCriticalSection(&globalLock); + } + EnterCriticalSection(&initLock); +} + +/* + *---------------------------------------------------------------------- + * + * TclpInitUnlock + * + * This procedure is used to release a lock that serializes + * initialization and finalization of Tcl. + * + * Results: + * None. + * + * Side effects: + * Release the initialization mutex. + * + *---------------------------------------------------------------------- + */ + +void +TclpInitUnlock(void) +{ + LeaveCriticalSection(&initLock); +} + +/* + *---------------------------------------------------------------------- + * + * TclpGlobalLock + * + * This procedure is used to grab a lock that serializes creation of + * mutexes, condition variables, and thread local storage keys. + * + * This lock must be different than the initLock because the initLock is + * held during creation of synchronization objects. + * + * Results: + * None. + * + * Side effects: + * Acquire the global mutex. + * + *---------------------------------------------------------------------- + */ + +void +TclpGlobalLock(void) +{ + if (!initialized) { + /* + * There is a fundamental race here that is solved by creating the + * first Tcl interpreter in a single threaded environment. Once the + * interpreter has been created, it is safe to create more threads + * that create interpreters in parallel. + */ + + initialized = 1; + InitializeCriticalSection(&joinLock); + InitializeCriticalSection(&initLock); + InitializeCriticalSection(&globalLock); + } + EnterCriticalSection(&globalLock); +} + +/* + *---------------------------------------------------------------------- + * + * TclpGlobalUnlock + * + * This procedure is used to release a lock that serializes creation and + * deletion of synchronization objects. + * + * Results: + * None. + * + * Side effects: + * Release the global mutex. + * + *---------------------------------------------------------------------- + */ + +void +TclpGlobalUnlock(void) +{ + LeaveCriticalSection(&globalLock); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_GetAllocMutex + * + * This procedure returns a pointer to a statically initialized mutex for + * use by the memory allocator. The allocator must use this lock, because + * all other locks are allocated... + * + * Results: + * A pointer to a mutex that is suitable for passing to Tcl_MutexLock and + * Tcl_MutexUnlock. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Mutex * +Tcl_GetAllocMutex(void) +{ +#if TCL_THREADS + if (!allocOnce) { + WMutexInit(&allocLock); + allocOnce = 1; + } + return (Tcl_Mutex *) &allocLockPtr; +#else + return NULL; +#endif +} + +/* + *---------------------------------------------------------------------- + * + * TclFinalizeLock + * + * This procedure is used to destroy all private resources used in this + * file. + * + * Results: + * None. + * + * Side effects: + * Destroys everything private. TclpInitLock must be held entering this + * function. + * + *---------------------------------------------------------------------- + */ + +void +TclFinalizeLock(void) +{ + TclpGlobalLock(); + DeleteCriticalSection(&joinLock); + + /* + * Destroy the critical section that we are holding! + */ + + DeleteCriticalSection(&globalLock); + initialized = 0; + +#if TCL_THREADS + if (allocOnce) { + WMutexDestroy(&allocLock); + allocOnce = 0; + } +#endif + + LeaveCriticalSection(&initLock); + + /* + * Destroy the critical section that we were holding. + */ + + DeleteCriticalSection(&initLock); +} + +#if TCL_THREADS + +static void +WMutexInit( + WMutex *wmPtr) +{ + wmPtr->thread = 0; + wmPtr->counter = 0; + InitializeCriticalSection(&wmPtr->crit); +} + +static void +WMutexDestroy( + WMutex *wmPtr) +{ + DeleteCriticalSection(&wmPtr->crit); + assert(wmPtr->thread == 0 && wmPtr->counter == 0); +} + +static void +WMutexLock( + WMutex *wmPtr) +{ + DWORD mythread = GetCurrentThreadId(); + + if (wmPtr->thread == mythread) { + // We owned the lock already, so it's recursive. + wmPtr->counter++; + } else { + // We don't own the lock, so we can safely lock it. Then we own it. + EnterCriticalSection(&wmPtr->crit); + wmPtr->thread = mythread; + } +} + +static void +WMutexUnlock( + WMutex *wmPtr) +{ + assert(wmPtr->thread == GetCurrentThreadId()); + if (wmPtr->counter) { + // It's recursive + wmPtr->counter--; + } else { + wmPtr->thread = 0; + LeaveCriticalSection(&wmPtr->crit); + } +} + +/* locally used prototype */ +static void FinalizeConditionEvent(void *data); + +/* + *---------------------------------------------------------------------- + * + * Tcl_MutexLock -- + * + * This procedure is invoked to lock a mutex. This is a self initializing + * mutex that is automatically finalized during Tcl_Finalize. + * + * Results: + * None. + * + * Side effects: + * May block the current thread. The mutex is acquired when this returns. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_MutexLock( + Tcl_Mutex *mutexPtr) /* Really (WMutex **) */ +{ + WMutex *wmPtr; + + if (*mutexPtr == NULL) { + TclpGlobalLock(); + + /* + * Double inside global lock check to avoid a race. + */ + + if (*mutexPtr == NULL) { + wmPtr = (WMutex *) Tcl_Alloc(sizeof(WMutex)); + WMutexInit(wmPtr); + *mutexPtr = (Tcl_Mutex) wmPtr; + TclRememberMutex(mutexPtr); + } + TclpGlobalUnlock(); + } + wmPtr = *((WMutex **)mutexPtr); + WMutexLock(wmPtr); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_MutexUnlock -- + * + * This procedure is invoked to unlock a mutex. + * + * Results: + * None. + * + * Side effects: + * The mutex is released when this returns. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_MutexUnlock( + Tcl_Mutex *mutexPtr) /* Really (WMutex **) */ +{ + WMutex *wmPtr = *((WMutex **)mutexPtr); + WMutexUnlock(wmPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TclpFinalizeMutex -- + * + * This procedure is invoked to clean up one mutex. This is only safe to + * call at the end of time. + * + * Results: + * None. + * + * Side effects: + * The mutex list is deallocated. + * + *---------------------------------------------------------------------- + */ + +void +TclpFinalizeMutex( + Tcl_Mutex *mutexPtr) /* Really (WMutex **) */ +{ + WMutex *wmPtr = *(WMutex **)mutexPtr; + + if (wmPtr != NULL) { + WMutexDestroy(wmPtr); + Tcl_Free(wmPtr); + *mutexPtr = NULL; + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ConditionWait -- + * + * This procedure is invoked to wait on a condition variable. The mutex + * is atomically released as part of the wait, and automatically grabbed + * when the condition is signaled. + * + * The mutex must be held when this procedure is called. + * + * Results: + * None. + * + * Side effects: + * May block the current thread. The mutex is acquired when this returns. + * Will allocate memory for a HANDLE and initialize this the first time + * this Tcl_Condition is used. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_ConditionWait( + Tcl_Condition *condPtr, /* Really (WinCondition **) */ + Tcl_Mutex *mutexPtr, /* Really (CRITICAL_SECTION **) */ + const Tcl_Time *timePtr) /* Timeout on waiting period */ +{ + WinCondition *winCondPtr; /* Per-condition queue head */ + WMutex *wmPtr; /* Caller's Mutex, after casting */ + DWORD wtime; /* Windows time value */ + int timeout; /* True if we got a timeout */ + int counter; /* Caller's Mutex counter */ + int doExit = 0; /* True if we need to do exit setup */ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* + * Self initialize the two parts of the condition. The per-condition and + * per-thread parts need to be handled independently. + */ + + if (tsdPtr->flags == WIN_THREAD_UNINIT) { + TclpGlobalLock(); + + /* + * Create the per-thread event and queue pointers. + */ + + if (tsdPtr->flags == WIN_THREAD_UNINIT) { + tsdPtr->condEvent = CreateEventW(NULL, TRUE /* manual reset */, + FALSE /* non signaled */, NULL); + tsdPtr->nextPtr = NULL; + tsdPtr->prevPtr = NULL; + tsdPtr->flags = WIN_THREAD_RUNNING; + doExit = 1; + } + TclpGlobalUnlock(); + + if (doExit) { + /* + * Create a per-thread exit handler to clean up the condEvent. We + * must be careful to do this outside the Global Lock because + * Tcl_CreateThreadExitHandler uses its own ThreadSpecificData, + * and initializing that may drop back into the Global Lock. + */ + + Tcl_CreateThreadExitHandler(FinalizeConditionEvent, tsdPtr); + } + } + + if (*condPtr == NULL) { + TclpGlobalLock(); + + /* + * Initialize the per-condition queue pointers and Mutex. + */ + + if (*condPtr == NULL) { + winCondPtr = (WinCondition *)Tcl_Alloc(sizeof(WinCondition)); + InitializeCriticalSection(&winCondPtr->condLock); + winCondPtr->firstPtr = NULL; + winCondPtr->lastPtr = NULL; + *condPtr = (Tcl_Condition) winCondPtr; + TclRememberCondition(condPtr); + } + TclpGlobalUnlock(); + } + wmPtr = *((WMutex **)mutexPtr); + winCondPtr = *((WinCondition **)condPtr); + if (timePtr == NULL) { + wtime = INFINITE; + } else { + wtime = (DWORD)timePtr->sec * 1000 + (DWORD)timePtr->usec / 1000; + } + + /* + * Queue the thread on the condition, using the per-condition lock for + * serialization. + */ + + tsdPtr->flags = WIN_THREAD_BLOCKED; + tsdPtr->nextPtr = NULL; + EnterCriticalSection(&winCondPtr->condLock); + tsdPtr->prevPtr = winCondPtr->lastPtr; /* A: */ + winCondPtr->lastPtr = tsdPtr; + if (tsdPtr->prevPtr != NULL) { + tsdPtr->prevPtr->nextPtr = tsdPtr; + } + if (winCondPtr->firstPtr == NULL) { + winCondPtr->firstPtr = tsdPtr; + } + + /* + * Unlock the caller's mutex and wait for the condition, or a timeout. + * There is a minor issue here in that we don't count down the timeout if + * we get notified, but another thread grabs the condition before we do. + * In that race condition we'll wait again for the full timeout. Timed + * waits are dubious anyway. Either you have the locking protocol wrong + * and are masking a deadlock, or you are using conditions to pause your + * thread. + */ + + counter = wmPtr->counter; + wmPtr->counter = 0; + DWORD mythread = GetCurrentThreadId(); + assert(wmPtr->thread == mythread); + wmPtr->thread = 0; + LeaveCriticalSection(&wmPtr->crit); + timeout = 0; + while (!timeout && (tsdPtr->flags & WIN_THREAD_BLOCKED)) { + ResetEvent(tsdPtr->condEvent); + LeaveCriticalSection(&winCondPtr->condLock); + if (WaitForSingleObjectEx(tsdPtr->condEvent, wtime, + TRUE) == WAIT_TIMEOUT) { + timeout = 1; + } + EnterCriticalSection(&winCondPtr->condLock); + } + + /* + * Be careful on timeouts because the signal might arrive right around the + * time limit and someone else could have taken us off the queue. + */ + + if (timeout) { + if (tsdPtr->flags & WIN_THREAD_RUNNING) { + timeout = 0; + } else { + /* + * When dequeueing, we can leave the tsdPtr->nextPtr and + * tsdPtr->prevPtr with dangling pointers because they are + * reinitialized w/out reading them when the thread is enqueued + * later. + */ + + if (winCondPtr->firstPtr == tsdPtr) { + winCondPtr->firstPtr = tsdPtr->nextPtr; + } else { + tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr; + } + if (winCondPtr->lastPtr == tsdPtr) { + winCondPtr->lastPtr = tsdPtr->prevPtr; + } else { + tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr; + } + tsdPtr->flags = WIN_THREAD_RUNNING; + } + } + + LeaveCriticalSection(&winCondPtr->condLock); + EnterCriticalSection(&wmPtr->crit); + wmPtr->counter = counter; + wmPtr->thread = mythread; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_ConditionNotify -- + * + * This procedure is invoked to signal a condition variable. + * + * The mutex must be held during this call to avoid races, but this + * interface does not enforce that. + * + * Results: + * None. + * + * Side effects: + * May unblock another thread. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_ConditionNotify( + Tcl_Condition *condPtr) +{ + WinCondition *winCondPtr; + ThreadSpecificData *tsdPtr; + + if (*condPtr != NULL) { + winCondPtr = *((WinCondition **)condPtr); + + if (winCondPtr == NULL) { + return; + } + + /* + * Loop through all the threads waiting on the condition and notify + * them (i.e., broadcast semantics). The queue manipulation is guarded + * by the per-condition coordinating mutex. + */ + + EnterCriticalSection(&winCondPtr->condLock); + while (winCondPtr->firstPtr != NULL) { + tsdPtr = winCondPtr->firstPtr; + winCondPtr->firstPtr = tsdPtr->nextPtr; + if (winCondPtr->lastPtr == tsdPtr) { + winCondPtr->lastPtr = NULL; + } + tsdPtr->flags = WIN_THREAD_RUNNING; + tsdPtr->nextPtr = NULL; + tsdPtr->prevPtr = NULL; /* Not strictly necessary, see A: */ + SetEvent(tsdPtr->condEvent); + } + LeaveCriticalSection(&winCondPtr->condLock); + } else { + /* + * No-one has used the condition variable, so there are no waiters. + */ + } +} + +/* + *---------------------------------------------------------------------- + * + * FinalizeConditionEvent -- + * + * This procedure is invoked to clean up the per-thread event used to + * implement condition waiting. This is only safe to call at the end of + * time. + * + * Results: + * None. + * + * Side effects: + * The per-thread event is closed. + * + *---------------------------------------------------------------------- + */ + +static void +FinalizeConditionEvent( + void *data) +{ + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) data; + + tsdPtr->flags = WIN_THREAD_UNINIT; + CloseHandle(tsdPtr->condEvent); +} + +/* + *---------------------------------------------------------------------- + * + * TclpFinalizeCondition -- + * + * This procedure is invoked to clean up a condition variable. This is + * only safe to call at the end of time. + * + * This assumes the Global Lock is held. + * + * Results: + * None. + * + * Side effects: + * The condition variable is deallocated. + * + *---------------------------------------------------------------------- + */ + +void +TclpFinalizeCondition( + Tcl_Condition *condPtr) +{ + WinCondition *winCondPtr = *(WinCondition **)condPtr; + + /* + * Note - this is called long after the thread-local storage is reclaimed. + * The per-thread condition waiting event is reclaimed earlier in a + * per-thread exit handler, which is called before thread local storage is + * reclaimed. + */ + + if (winCondPtr != NULL) { + DeleteCriticalSection(&winCondPtr->condLock); + Tcl_Free(winCondPtr); + *condPtr = NULL; + } +} + +/* + * Additions by AOL for specialized thread memory allocator. + */ +#ifdef USE_THREAD_ALLOC + +Tcl_Mutex * +TclpNewAllocMutex(void) +{ + allocMutex *lockPtr; + + lockPtr = (allocMutex *)malloc(sizeof(allocMutex)); + if (lockPtr == NULL) { + Tcl_Panic("could not allocate lock"); + } + lockPtr->tlock = (Tcl_Mutex)&lockPtr->wm; + WMutexInit(&lockPtr->wm); + return &lockPtr->tlock; +} + +void +TclpFreeAllocMutex( + Tcl_Mutex *mutex) /* The alloc mutex to free. */ +{ + allocMutex *lockPtr = (allocMutex *) mutex; + + if (!lockPtr || !lockPtr->tlock) { + return; + } + lockPtr->tlock = NULL; + WMutexDestroy(&lockPtr->wm); + free(lockPtr); +} + +void +TclpInitAllocCache(void) +{ + /* + * We need to make sure that TclpFreeAllocCache is called on each + * thread that calls this, but only on threads that call this. + */ + + tlsKey = TlsAlloc(); + if (tlsKey == TLS_OUT_OF_INDEXES) { + Tcl_Panic("could not allocate thread local storage"); + } +} + +void * +TclpGetAllocCache(void) +{ + void *result; + result = TlsGetValue(tlsKey); + if ((result == NULL) && (GetLastError() != NO_ERROR)) { + Tcl_Panic("TlsGetValue failed from TclpGetAllocCache"); + } + return result; +} + +void +TclpSetAllocCache( + void *ptr) +{ + BOOL success; + success = TlsSetValue(tlsKey, ptr); + if (!success) { + Tcl_Panic("TlsSetValue failed from TclpSetAllocCache"); + } +} + +void +TclpFreeAllocCache( + void *ptr) +{ + BOOL success; + + if (ptr != NULL) { + /* + * Called by TclFinalizeThreadAlloc() and + * TclFinalizeThreadAllocThread() during Tcl_Finalize() or + * Tcl_FinalizeThread(). This function destroys the tsd key which + * stores allocator caches in thread local storage. + */ + + TclFreeAllocCache(ptr); + success = TlsSetValue(tlsKey, NULL); + if (!success) { + Tcl_Panic("TlsSetValue failed from TclpFreeAllocCache"); + } + } else { + /* + * Called by us in TclFinalizeThreadAlloc() during the library + * finalization initiated from Tcl_Finalize() + */ + + success = TlsFree(tlsKey); + if (!success) { + Tcl_Panic("TlsFree failed from TclpFreeAllocCache"); + } + } +} +#endif /* USE_THREAD_ALLOC */ + +void * +TclpThreadCreateKey(void) +{ + DWORD *key; + + key = (DWORD *)TclpSysAlloc(sizeof *key); + if (key == NULL) { + Tcl_Panic("unable to allocate thread key!"); + } + + *key = TlsAlloc(); + + if (*key == TLS_OUT_OF_INDEXES) { + Tcl_Panic("unable to allocate thread-local storage"); + } + + return key; +} + +void +TclpThreadDeleteKey( + void *keyPtr) +{ + DWORD *key = (DWORD *)keyPtr; + + if (!TlsFree(*key)) { + Tcl_Panic("unable to delete key"); + } + + TclpSysFree(keyPtr); +} + +void +TclpThreadSetGlobalTSD( + void *tsdKeyPtr, + void *ptr) +{ + DWORD *key = (DWORD *)tsdKeyPtr; + + if (!TlsSetValue(*key, ptr)) { + Tcl_Panic("unable to set global TSD value"); + } +} + +void * +TclpThreadGetGlobalTSD( + void *tsdKeyPtr) +{ + DWORD *key = (DWORD *)tsdKeyPtr; + + return TlsGetValue(*key); +} + +#endif /* TCL_THREADS */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclWinTime.c b/vendor/tcl/win/tclWinTime.c new file mode 100644 index 00000000..c0d2b2ed --- /dev/null +++ b/vendor/tcl/win/tclWinTime.c @@ -0,0 +1,1246 @@ +/* + * tclWinTime.c -- + * + * Contains Windows specific versions of Tcl functions that obtain time + * values from the operating system. + * + * Copyright © 1995-1998 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" + +/* + * Number of samples over which to estimate the performance counter. + */ + +#define SAMPLES 64 + +/* + * Data for managing high-resolution timers. + */ + +typedef struct { + CRITICAL_SECTION cs; /* Mutex guarding this structure. */ + int initialized; /* Flag == 1 if this structure is + * initialized. */ + int perfCounterAvailable; /* Flag == 1 if the hardware has a performance + * counter. */ + DWORD calibrationInterv; /* Calibration interval in seconds (start 1 + * sec) */ + HANDLE calibrationThread; /* Handle to the thread that keeps the virtual + * clock calibrated. */ + HANDLE readyEvent; /* System event used to trigger the requesting + * thread when the clock calibration procedure + * is initialized for the first time. */ + HANDLE exitEvent; /* Event to signal out of an exit handler to + * tell the calibration loop to terminate. */ + LARGE_INTEGER nominalFreq; /* Nominal frequency of the system performance + * counter, that is, the value returned from + * QueryPerformanceFrequency. */ + /* + * The following values are used for calculating virtual time. Virtual + * time is always equal to: + * lastFileTime + (current perf counter - lastCounter) + * * 10000000 / curCounterFreq + * and lastFileTime and lastCounter are updated any time that virtual time + * is returned to a caller. + */ + + ULARGE_INTEGER fileTimeLastCall; + LARGE_INTEGER perfCounterLastCall; + LARGE_INTEGER curCounterFreq; + LARGE_INTEGER posixEpoch; /* Posix epoch expressed as 100-ns ticks since + * the windows epoch. */ + + /* + * Data used in developing the estimate of performance counter frequency + */ + + unsigned long long fileTimeSample[SAMPLES]; + /* Last 64 samples of system time. */ + long long perfCounterSample[SAMPLES]; + /* Last 64 samples of performance counter. */ + int sampleNo; /* Current sample number. */ +} TimeInfo; + +static TimeInfo timeInfo = { + { NULL, 0, 0, NULL, NULL, 0 }, + 0, + 0, + 1, + (HANDLE) NULL, + (HANDLE) NULL, + (HANDLE) NULL, +#if defined(HAVE_CAST_TO_UNION) && !defined(__cplusplus) + (LARGE_INTEGER) (long long) 0, + (ULARGE_INTEGER) (DWORDLONG) 0, + (LARGE_INTEGER) (long long) 0, + (LARGE_INTEGER) (long long) 0, + (LARGE_INTEGER) (long long) 0, +#else + {{0, 0}}, + {{0, 0}}, + {{0, 0}}, + {{0, 0}}, + {{0, 0}}, +#endif + { 0 }, + { 0 }, + 0 +}; + +/* + * Scale to convert wide click values from the TclpGetWideClicks native + * resolution to microsecond resolution and back. + */ +static struct { + int initialized; /* 1 if initialized, 0 otherwise */ + int perfCounter; /* 1 if performance counter usable for wide + * clicks */ + double microsecsScale; /* Denominator scale between clock / microsecs */ +} wideClick = {0, 0, 0.0}; + +/* + * Declarations for functions defined later in this file. + */ + +static void StopCalibration(void *clientData); +static DWORD WINAPI CalibrationThread(LPVOID arg); +static void UpdateTimeEachSecond(void); +static void ResetCounterSamples(unsigned long long fileTime, + long long perfCounter, long long perfFreq); +static long long AccumulateSample(long long perfCounter, + unsigned long long fileTime); +static void NativeScaleTime(Tcl_Time* timebuf, + void *clientData); +static long long NativeGetMicroseconds(void); +static void NativeGetTime(Tcl_Time* timebuf, + void *clientData); + +/* + * TIP #233 (Virtualized Time): Data for the time hooks, if any. + */ + +Tcl_GetTimeProc *tclGetTimeProcPtr = NativeGetTime; +Tcl_ScaleTimeProc *tclScaleTimeProcPtr = NativeScaleTime; +void *tclTimeClientData = NULL; + +/* + * Inlined version of Tcl_GetTime. + */ + +static inline void +GetTime( + Tcl_Time *timePtr) +{ + tclGetTimeProcPtr(timePtr, tclTimeClientData); +} + +static inline int +IsTimeNative(void) +{ + return tclGetTimeProcPtr == NativeGetTime; +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetSeconds -- + * + * This procedure returns the number of seconds from the epoch. On most + * Unix systems the epoch is Midnight Jan 1, 1970 GMT. + * + * Results: + * Number of seconds from the epoch. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +unsigned long long +TclpGetSeconds(void) +{ + long long usecSincePosixEpoch; + + /* + * Try to use high resolution timer + */ + + if (IsTimeNative() && (usecSincePosixEpoch = NativeGetMicroseconds())) { + return usecSincePosixEpoch / 1000000; + } else { + Tcl_Time t; + + GetTime(&t); + return (unsigned long long)t.sec; + } +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetClicks -- + * + * This procedure returns a value that represents the highest resolution + * clock available on the system. There are no guarantees on what the + * resolution will be. In Tcl we will call this value a "click". The + * start time is also system dependent. + * + * Results: + * Number of clicks from some start time. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +unsigned long long +TclpGetClicks(void) +{ + long long usecSincePosixEpoch; + + /* + * Try to use high resolution timer. + */ + + if (IsTimeNative() && (usecSincePosixEpoch = NativeGetMicroseconds())) { + return (Tcl_WideUInt) usecSincePosixEpoch; + } else { + /* + * Use the Tcl_GetTime abstraction to get the time in microseconds, as + * nearly as we can, and return it. + */ + + Tcl_Time now; /* Current Tcl time */ + + GetTime(&now); + return ((unsigned long long)(now.sec)*1000000ULL) + + (unsigned long long)(now.usec); + } +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetWideClicks -- + * + * This procedure returns a WideInt value that represents the highest + * resolution clock in microseconds available on the system. + * + * Results: + * Number of microseconds (from some start time). + * + * Side effects: + * This should be used for time-delta resp. for measurement purposes + * only, because on some platforms can return microseconds from some + * start time (not from the epoch). + * + *---------------------------------------------------------------------- + */ + +long long +TclpGetWideClicks(void) +{ + LARGE_INTEGER curCounter; + + if (!wideClick.initialized) { + LARGE_INTEGER perfCounterFreq; + + /* + * The frequency of the performance counter is fixed at system boot and + * is consistent across all processors. Therefore, the frequency need + * only be queried upon application initialization. + */ + + if (QueryPerformanceFrequency(&perfCounterFreq)) { + wideClick.perfCounter = 1; + wideClick.microsecsScale = 1000000.0 / (double)perfCounterFreq.QuadPart; + } else { + /* fallback using microseconds */ + wideClick.perfCounter = 0; + wideClick.microsecsScale = 1; + } + + wideClick.initialized = 1; + } + if (wideClick.perfCounter) { + if (QueryPerformanceCounter(&curCounter)) { + return (long long)curCounter.QuadPart; + } + /* fallback using microseconds */ + wideClick.perfCounter = 0; + wideClick.microsecsScale = 1; + return TclpGetMicroseconds(); + } else { + return TclpGetMicroseconds(); + } +} + +/* + *---------------------------------------------------------------------- + * + * TclpWideClickInMicrosec -- + * + * This procedure return scale to convert wide click values from the + * TclpGetWideClicks native resolution to microsecond resolution + * and back. + * + * Results: + * 1 click in microseconds as double. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +double +TclpWideClickInMicrosec(void) +{ + if (!wideClick.initialized) { + (void) TclpGetWideClicks(); /* initialize */ + } + return wideClick.microsecsScale; +} + +/* + *---------------------------------------------------------------------- + * + * TclpGetMicroseconds -- + * + * This procedure returns a WideInt value that represents the highest + * resolution clock in microseconds available on the system. + * + * Results: + * Number of microseconds (from the epoch). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +long long +TclpGetMicroseconds(void) +{ + long long usecSincePosixEpoch; + + /* + * Try to use high resolution timer. + */ + + if (IsTimeNative() && (usecSincePosixEpoch = NativeGetMicroseconds())) { + return usecSincePosixEpoch; + } else { + /* + * Use the Tcl_GetTime abstraction to get the time in microseconds, as + * nearly as we can, and return it. + */ + + Tcl_Time now; + + GetTime(&now); + return now.sec * 1000000 + now.usec; + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_GetTime -- + * + * Gets the current system time in seconds and microseconds since the + * beginning of the epoch: 00:00 UCT, January 1, 1970. + * + * Results: + * Returns the current time in timePtr. + * + * Side effects: + * On the first call, initializes a set of static variables to keep track + * of the base value of the performance counter, the corresponding wall + * clock (obtained through ftime) and the frequency of the performance + * counter. Also spins a thread whose function is to wake up periodically + * and monitor these values, adjusting them as necessary to correct for + * drift in the performance counter's oscillator. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_GetTime( + Tcl_Time *timePtr) /* Location to store time information. */ +{ + long long usecSincePosixEpoch; + + /* + * Try to use high resolution timer. + */ + + if (IsTimeNative() && (usecSincePosixEpoch = NativeGetMicroseconds())) { + timePtr->sec = usecSincePosixEpoch / 1000000; + timePtr->usec = (long)(usecSincePosixEpoch % 1000000); + } else { + GetTime(timePtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * NativeScaleTime -- + * + * TIP #233: Scale from virtual time to the real-time. For native scaling + * the relationship is 1:1 and nothing has to be done. + * + * Results: + * Scales the time in timePtr. + * + * Side effects: + * See above. + * + *---------------------------------------------------------------------- + */ + +static void +NativeScaleTime( + TCL_UNUSED(Tcl_Time *), + TCL_UNUSED(void *)) +{ + /* + * Native scale is 1:1. Nothing is done. + */ +} + +/* + *---------------------------------------------------------------------- + * + * IsPerfCounterAvailable -- + * + * Tests whether the performance counter is available, which is a gnarly + * problem on 32-bit systems. Also retrieves the nominal frequency of the + * performance counter. + * + * Results: + * 1 if the counter is available, 0 if not. + * + * Side effects: + * Updates fields of the timeInfo global. Make sure you hold the lock + * before calling this. + * + *---------------------------------------------------------------------- + */ + +static inline int +IsPerfCounterAvailable(void) +{ + timeInfo.perfCounterAvailable = + QueryPerformanceFrequency(&timeInfo.nominalFreq); + + /* + * Some hardware abstraction layers use the CPU clock in place of the + * real-time clock as a performance counter reference. This results in: + * - inconsistent results among the processors on multi-processor + * systems. + * - unpredictable changes in performance counter frequency on + * "gearshift" processors such as Transmeta and SpeedStep. + * + * There seems to be no way to test whether the performance counter is + * reliable, but a useful heuristic is that if its frequency is 1.193182 + * MHz or 3.579545 MHz, it's derived from a colorburst crystal and is + * therefore the RTC rather than the TSC. + * + * A sloppier but serviceable heuristic is that the RTC crystal is + * normally less than 15 MHz while the TSC crystal is virtually assured to + * be greater than 100 MHz. Since Win98SE appears to fiddle with the + * definition of the perf counter frequency (perhaps in an attempt to + * calibrate the clock?), we use the latter rule rather than an exact + * match. + * + * We also assume (perhaps questionably) that the vendors have gotten + * their act together on Win64, so bypass all this rubbish on that + * platform. + */ + +#if !defined(_WIN64) + if (timeInfo.perfCounterAvailable && + /* + * The following lines would do an exact match on crystal + * frequency: + * + * timeInfo.nominalFreq.QuadPart != (long long) 1193182 && + * timeInfo.nominalFreq.QuadPart != (long long) 3579545 && + */ + timeInfo.nominalFreq.QuadPart > (long long) 15000000) { + /* + * As an exception, if every logical processor on the system is on the + * same chip, we use the performance counter anyway, presuming that + * everyone's TSC is locked to the same oscillator. + */ + + SYSTEM_INFO systemInfo; + int regs[4]; + + GetSystemInfo(&systemInfo); + if (TclWinCPUID(0, regs) == TCL_OK + && regs[1] == 0x756E6547 /* "Genu" */ + && regs[3] == 0x49656E69 /* "ineI" */ + && regs[2] == 0x6C65746E /* "ntel" */ + && TclWinCPUID(1, regs) == TCL_OK + && ((regs[0]&0x00000F00) == 0x00000F00 /* Pentium 4 */ + || ((regs[0] & 0x00F00000) /* Extended family */ + && (regs[3] & 0x10000000))) /* Hyperthread */ + && (((regs[1]&0x00FF0000) >> 16)/* CPU count */ + == (int)systemInfo.dwNumberOfProcessors)) { + timeInfo.perfCounterAvailable = TRUE; + } else { + timeInfo.perfCounterAvailable = FALSE; + } + } +#endif /* above code is Win32 only */ + + return timeInfo.perfCounterAvailable; +} + +/* + *---------------------------------------------------------------------- + * + * NativeGetMicroseconds -- + * + * Gets the current system time in microseconds since the beginning + * of the epoch: 00:00 UCT, January 1, 1970. + * + * Results: + * Returns the wide integer with number of microseconds from the epoch, or + * 0 if high resolution timer is not available. + * + * Side effects: + * On the first call, initializes a set of static variables to keep track + * of the base value of the performance counter, the corresponding wall + * clock (obtained through ftime) and the frequency of the performance + * counter. Also spins a thread whose function is to wake up periodically + * and monitor these values, adjusting them as necessary to correct for + * drift in the performance counter's oscillator. + * + *---------------------------------------------------------------------- + */ + +static inline long long +NativeCalc100NsTicks( + ULONGLONG fileTimeLastCall, + LONGLONG perfCounterLastCall, + LONGLONG curCounterFreq, + LONGLONG curCounter) +{ + return fileTimeLastCall + + ((curCounter - perfCounterLastCall) * 10000000 / curCounterFreq); +} + +static long long +NativeGetMicroseconds(void) +{ + /* + * Initialize static storage on the first trip through. + * + * Note: Outer check for 'initialized' is a performance win since it + * avoids an extra mutex lock in the common case. + */ + + if (!timeInfo.initialized) { + TclpInitLock(); + if (!timeInfo.initialized) { + timeInfo.posixEpoch.LowPart = 0xD53E8000; + timeInfo.posixEpoch.HighPart = 0x019DB1DE; + + /* + * If the performance counter is available, start a thread to + * calibrate it. + */ + + if (IsPerfCounterAvailable()) { + DWORD id; + + InitializeCriticalSection(&timeInfo.cs); + timeInfo.readyEvent = CreateEventW(NULL, FALSE, FALSE, NULL); + timeInfo.exitEvent = CreateEventW(NULL, FALSE, FALSE, NULL); + timeInfo.calibrationThread = CreateThread(NULL, 256, + CalibrationThread, (LPVOID) NULL, 0, &id); + SetThreadPriority(timeInfo.calibrationThread, + THREAD_PRIORITY_HIGHEST); + + /* + * Wait for the thread just launched to start running, and + * create an exit handler that kills it so that it doesn't + * outlive unloading tclXX.dll + */ + + WaitForSingleObject(timeInfo.readyEvent, INFINITE); + CloseHandle(timeInfo.readyEvent); + Tcl_CreateExitHandler(StopCalibration, NULL); + } + timeInfo.initialized = TRUE; + } + TclpInitUnlock(); + } + + if (timeInfo.perfCounterAvailable && timeInfo.curCounterFreq.QuadPart!=0) { + /* + * Query the performance counter and use it to calculate the current + * time. + */ + + ULONGLONG fileTimeLastCall; + LONGLONG perfCounterLastCall, curCounterFreq; + /* Copy with current data of calibration + * cycle. */ + LARGE_INTEGER curCounter; + /* Current performance counter. */ + + QueryPerformanceCounter(&curCounter); + + /* + * Hold time section locked as short as possible + */ + + EnterCriticalSection(&timeInfo.cs); + + fileTimeLastCall = timeInfo.fileTimeLastCall.QuadPart; + perfCounterLastCall = timeInfo.perfCounterLastCall.QuadPart; + curCounterFreq = timeInfo.curCounterFreq.QuadPart; + + LeaveCriticalSection(&timeInfo.cs); + + /* + * If calibration cycle occurred after we get curCounter + */ + + if (curCounter.QuadPart <= perfCounterLastCall) { + /* + * Calibrated file-time is saved from Posix in 100-ns ticks + */ + + return fileTimeLastCall / 10; + } + + /* + * If it appears to be more than 1.1 seconds since the last trip + * through the calibration loop, the performance counter may have + * jumped forward. (See MSDN Knowledge Base article Q274323 for a + * description of the hardware problem that makes this test + * necessary.) If the counter jumps, we don't want to use it directly. + * Instead, we must return system time. Eventually, the calibration + * loop should recover. + */ + + if (curCounter.QuadPart - perfCounterLastCall < + 11 * curCounterFreq * timeInfo.calibrationInterv / 10) { + /* + * Calibrated file-time is saved from Posix in 100-ns ticks. + */ + + return NativeCalc100NsTicks(fileTimeLastCall, + perfCounterLastCall, curCounterFreq, + curCounter.QuadPart) / 10; + } + } + + /* + * High resolution timer is not available. + */ + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * NativeGetTime -- + * + * TIP #233: Gets the current system time in seconds and microseconds + * since the beginning of the epoch: 00:00 UCT, January 1, 1970. + * + * Results: + * Returns the current time in timePtr. + * + * Side effects: + * See NativeGetMicroseconds for more information. + * + *---------------------------------------------------------------------- + */ + +static void +NativeGetTime( + Tcl_Time *timePtr, + TCL_UNUSED(void *)) +{ + long long usecSincePosixEpoch; + + /* + * Try to use high resolution timer. + */ + + usecSincePosixEpoch = NativeGetMicroseconds(); + if (usecSincePosixEpoch) { + timePtr->sec = usecSincePosixEpoch / 1000000; + timePtr->usec = (long)(usecSincePosixEpoch % 1000000); + } else { + /* + * High resolution timer is not available. Just use ftime. + */ + + struct _timeb t; + + _ftime(&t); + timePtr->sec = t.time; + timePtr->usec = t.millitm * 1000; + } +} + +/* + *---------------------------------------------------------------------- + * + * StopCalibration -- + * + * Turns off the calibration thread in preparation for exiting the + * process. + * + * Results: + * None. + * + * Side effects: + * Sets the 'exitEvent' event in the 'timeInfo' structure to ask the + * thread in question to exit, and waits for it to do so. + * + *---------------------------------------------------------------------- + */ + +void TclWinResetTimerResolution(void); + +static void +StopCalibration( + TCL_UNUSED(void *)) +{ + SetEvent(timeInfo.exitEvent); + + /* + * If Tcl_Finalize was called from DllMain, the calibration thread is in a + * paused state so we need to timeout and continue. + */ + + WaitForSingleObject(timeInfo.calibrationThread, 100); + CloseHandle(timeInfo.exitEvent); + CloseHandle(timeInfo.calibrationThread); +} + +/* + *---------------------------------------------------------------------- + * + * CalibrationThread -- + * + * Thread that manages calibration of the hi-resolution time derived from + * the performance counter, to keep it synchronized with the system + * clock. + * + * Parameters: + * arg - Client data from the CreateThread call. This parameter points to + * the static TimeInfo structure. + * + * Return value: + * None. This thread embeds an infinite loop. + * + * Side effects: + * At an interval of 1s, this thread performs virtual time discipline. + * + * Note: When this thread is entered, TclpInitLock has been called to + * safeguard the static storage. There is therefore no synchronization in the + * body of this procedure. + * + *---------------------------------------------------------------------- + */ + +static DWORD WINAPI +CalibrationThread( + TCL_UNUSED(LPVOID)) +{ + FILETIME curFileTime; + DWORD waitResult; + + /* + * Get initial system time and performance counter. + */ + + GetSystemTimeAsFileTime(&curFileTime); + QueryPerformanceCounter(&timeInfo.perfCounterLastCall); + QueryPerformanceFrequency(&timeInfo.curCounterFreq); + timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime; + timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime; + + /* + * Calibrated file-time will be saved from Posix in 100-ns ticks. + */ + + timeInfo.fileTimeLastCall.QuadPart -= timeInfo.posixEpoch.QuadPart; + + ResetCounterSamples(timeInfo.fileTimeLastCall.QuadPart, + timeInfo.perfCounterLastCall.QuadPart, + timeInfo.curCounterFreq.QuadPart); + + /* + * Wake up the calling thread. When it wakes up, it will release the + * initialization lock. + */ + + SetEvent(timeInfo.readyEvent); + + /* + * Run the calibration once a second. + */ + + while (timeInfo.perfCounterAvailable) { + /* + * If the exitEvent is set, break out of the loop. + */ + + waitResult = WaitForSingleObjectEx(timeInfo.exitEvent, 1000, FALSE); + if (waitResult == WAIT_OBJECT_0) { + break; + } + UpdateTimeEachSecond(); + } + + return (DWORD) 0; +} + +/* + *---------------------------------------------------------------------- + * + * UpdateTimeEachSecond -- + * + * Callback from the waitable timer in the clock calibration thread that + * updates system time. + * + * Parameters: + * info - Pointer to the static TimeInfo structure + * + * Results: + * None. + * + * Side effects: + * Performs virtual time calibration discipline. + * + *---------------------------------------------------------------------- + */ + +static void +UpdateTimeEachSecond(void) +{ + LARGE_INTEGER curPerfCounter; + /* Current value returned from + * QueryPerformanceCounter. */ + FILETIME curSysTime; /* Current system time. */ + static LARGE_INTEGER lastFileTime; + /* File time of the previous calibration */ + LARGE_INTEGER curFileTime; /* File time at the time this callback was + * scheduled. */ + long long estFreq; /* Estimated perf counter frequency. */ + long long vt0; /* Tcl time right now. */ + long long vt1; /* Tcl time one second from now. */ + long long tdiff; /* Difference between system clock and Tcl + * time. */ + long long driftFreq; /* Frequency needed to drift virtual time into + * step over 1 second. */ + + /* + * Sample performance counter and system time (from Posix epoch). + */ + + GetSystemTimeAsFileTime(&curSysTime); + curFileTime.LowPart = curSysTime.dwLowDateTime; + curFileTime.HighPart = curSysTime.dwHighDateTime; + curFileTime.QuadPart -= timeInfo.posixEpoch.QuadPart; + + /* + * If calibration still not needed (check for possible time switch) + */ + + if (curFileTime.QuadPart > lastFileTime.QuadPart && curFileTime.QuadPart < + lastFileTime.QuadPart + (timeInfo.calibrationInterv * 10000000)) { + /* + * Look again in next one second. + */ + + return; + } + QueryPerformanceCounter(&curPerfCounter); + + lastFileTime.QuadPart = curFileTime.QuadPart; + + /* + * We divide by timeInfo.curCounterFreq.QuadPart in several places. That + * value should always be positive on a correctly functioning system. But + * it is good to be defensive about such matters. So if something goes + * wrong and the value does goes to zero, we clear the + * timeInfo.perfCounterAvailable in order to cause the calibration thread + * to shut itself down, then return without additional processing. + */ + + if (timeInfo.curCounterFreq.QuadPart == 0){ + timeInfo.perfCounterAvailable = 0; + return; + } + + /* + * Several things may have gone wrong here that have to be checked for. + * (1) The performance counter may have jumped. + * (2) The system clock may have been reset. + * + * In either case, we'll need to reinitialize the circular buffer with + * samples relative to the current system time and the NOMINAL performance + * frequency (not the actual, because the actual has probably run slow in + * the first case). Our estimated frequency will be the nominal frequency. + * + * Store the current sample into the circular buffer of samples, and + * estimate the performance counter frequency. + */ + + estFreq = AccumulateSample(curPerfCounter.QuadPart, + (unsigned long long) curFileTime.QuadPart); + + /* + * We want to adjust things so that time appears to be continuous. + * Virtual file time, right now, is + * + * vt0 = 10000000 * (curPerfCounter - perfCounterLastCall) + * / curCounterFreq + * + fileTimeLastCall + * + * Ideally, we would like to drift the clock into place over a period of 2 + * sec, so that virtual time 2 sec from now will be + * + * vt1 = 20000000 + curFileTime + * + * The frequency that we need to use to drift the counter back into place + * is estFreq * 20000000 / (vt1 - vt0) + */ + + vt0 = NativeCalc100NsTicks(timeInfo.fileTimeLastCall.QuadPart, + timeInfo.perfCounterLastCall.QuadPart, + timeInfo.curCounterFreq.QuadPart, curPerfCounter.QuadPart); + + /* + * If we've gotten more than a second away from system time, then drifting + * the clock is going to be pretty hopeless. Just let it jump. Otherwise, + * compute the drift frequency and fill in everything. + */ + + tdiff = vt0 - curFileTime.QuadPart; + if (tdiff > 10000000 || tdiff < -10000000) { + /* + * Jump to current system time, use curent estimated frequency. + */ + + vt0 = curFileTime.QuadPart; + } else { + /* + * Calculate new frequency and estimate drift to the next second. + */ + + vt1 = 20000000 + curFileTime.QuadPart; + driftFreq = (estFreq * 20000000 / (vt1 - vt0)); + + /* + * Avoid too large drifts (only half of the current difference), that + * allows also be more accurate (aspire to the smallest tdiff), so + * then we can prolong calibration interval by tdiff < 100000 + */ + + driftFreq = timeInfo.curCounterFreq.QuadPart + + (driftFreq - timeInfo.curCounterFreq.QuadPart) / 2; + + /* + * Average between estimated, 2 current and 5 drifted frequencies, + * (do the soft drifting as possible) + */ + + estFreq = (estFreq + 2 * timeInfo.curCounterFreq.QuadPart + + 5 * driftFreq) / 8; + } + + /* + * Avoid too large discrepancy from nominal frequency. + */ + + if (estFreq > 1003 * timeInfo.nominalFreq.QuadPart / 1000) { + estFreq = 1003 * timeInfo.nominalFreq.QuadPart / 1000; + vt0 = curFileTime.QuadPart; + } else if (estFreq < 997 * timeInfo.nominalFreq.QuadPart / 1000) { + estFreq = 997 * timeInfo.nominalFreq.QuadPart / 1000; + vt0 = curFileTime.QuadPart; + } else if (vt0 != curFileTime.QuadPart) { + /* + * Be sure the clock ticks never backwards (avoid it by negative + * drifting). Just compare native time (in 100-ns) before and + * hereafter using new calibrated values) and do a small adjustment + * (short time freeze). + */ + + LARGE_INTEGER newPerfCounter; + long long nt0, nt1; + + QueryPerformanceCounter(&newPerfCounter); + nt0 = NativeCalc100NsTicks(timeInfo.fileTimeLastCall.QuadPart, + timeInfo.perfCounterLastCall.QuadPart, + timeInfo.curCounterFreq.QuadPart, newPerfCounter.QuadPart); + nt1 = NativeCalc100NsTicks(vt0, + curPerfCounter.QuadPart, estFreq, newPerfCounter.QuadPart); + if (nt0 > nt1) { + /* + * Drifted backwards, try to compensate with new base. + * + * First adjust with a micro jump (short frozen time is + * acceptable). + */ + + vt0 += nt0 - nt1; + + /* + * If drift unavoidable (e. g. we had a time switch), then reset + * it. + */ + + vt1 = vt0 - curFileTime.QuadPart; + if (vt1 > 10000000 || vt1 < -10000000) { + /* + * Larger jump resp. shift relative new file-time. + */ + + vt0 = curFileTime.QuadPart; + } + } + } + + /* + * In lock commit new values to timeInfo (hold lock as short as possible) + */ + + EnterCriticalSection(&timeInfo.cs); + + /* + * Grow calibration interval up to 10 seconds (if still precise enough) + */ + + if (tdiff < -100000 || tdiff > 100000) { + /* + * Too long drift. Reset calibration interval to 1000 second. + */ + + timeInfo.calibrationInterv = 1; + } else if (timeInfo.calibrationInterv < 10) { + timeInfo.calibrationInterv++; + } + + timeInfo.fileTimeLastCall.QuadPart = vt0; + timeInfo.curCounterFreq.QuadPart = estFreq; + timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart; + + LeaveCriticalSection(&timeInfo.cs); +} + +/* + *---------------------------------------------------------------------- + * + * ResetCounterSamples -- + * + * Fills the sample arrays in 'timeInfo' with dummy values that will + * yield the current performance counter and frequency. + * + * Results: + * None. + * + * Side effects: + * The array of samples is filled in so that it appears that there are + * SAMPLES samples at one-second intervals, separated by precisely the + * given frequency. + * + *---------------------------------------------------------------------- + */ + +static void +ResetCounterSamples( + unsigned long long fileTime,/* Current file time */ + long long perfCounter, /* Current performance counter */ + long long perfFreq) /* Target performance frequency */ +{ + int i; + + for (i = SAMPLES - 1 ; i >= 0 ; --i) { + timeInfo.perfCounterSample[i] = perfCounter; + timeInfo.fileTimeSample[i] = fileTime; + perfCounter -= perfFreq; + fileTime -= 10000000; + } + timeInfo.sampleNo = 0; +} + +/* + *---------------------------------------------------------------------- + * + * AccumulateSample -- + * + * Updates the circular buffer of performance counter and system time + * samples with a new data point. + * + * Results: + * None. + * + * Side effects: + * The new data point replaces the oldest point in the circular buffer, + * and the descriptive statistics are updated to accumulate the new + * point. + * + * Several things may have gone wrong here that have to be checked for. + * (1) The performance counter may have jumped. + * (2) The system clock may have been reset. + * + * In either case, we'll need to reinitialize the circular buffer with samples + * relative to the current system time and the NOMINAL performance frequency + * (not the actual, because the actual has probably run slow in the first + * case). + */ + +static long long +AccumulateSample( + long long perfCounter, + unsigned long long fileTime) +{ + unsigned long long workFTSample; + /* File time sample being removed from or + * added to the circular buffer. */ + long long workPCSample; /* Performance counter sample being removed + * from or added to the circular buffer. */ + unsigned long long lastFTSample; + /* Last file time sample recorded */ + long long lastPCSample; /* Last performance counter sample recorded */ + long long FTdiff; /* Difference between last FT and current */ + long long PCdiff; /* Difference between last PC and current */ + long long estFreq; /* Estimated performance counter frequency */ + + /* + * Test for jumps and reset the samples if we have one. + */ + + if (timeInfo.sampleNo == 0) { + lastPCSample = + timeInfo.perfCounterSample[timeInfo.sampleNo + SAMPLES - 1]; + lastFTSample = + timeInfo.fileTimeSample[timeInfo.sampleNo + SAMPLES - 1]; + } else { + lastPCSample = timeInfo.perfCounterSample[timeInfo.sampleNo - 1]; + lastFTSample = timeInfo.fileTimeSample[timeInfo.sampleNo - 1]; + } + + PCdiff = perfCounter - lastPCSample; + FTdiff = fileTime - lastFTSample; + if (PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10 + || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10 + || FTdiff < 9000000 || FTdiff > 11000000) { + ResetCounterSamples(fileTime, perfCounter, + timeInfo.nominalFreq.QuadPart); + return timeInfo.nominalFreq.QuadPart; + } else { + /* + * Estimate the frequency. + */ + + workPCSample = timeInfo.perfCounterSample[timeInfo.sampleNo]; + workFTSample = timeInfo.fileTimeSample[timeInfo.sampleNo]; + estFreq = 10000000 * (perfCounter - workPCSample) + / (fileTime - workFTSample); + timeInfo.perfCounterSample[timeInfo.sampleNo] = perfCounter; + timeInfo.fileTimeSample[timeInfo.sampleNo] = (long long) fileTime; + + /* + * Advance the sample number. + */ + + if (++timeInfo.sampleNo >= SAMPLES) { + timeInfo.sampleNo = 0; + } + + return estFreq; + } +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_SetTimeProc -- + * + * TIP #233 (Virtualized Time): Registers two handlers for the + * virtualization of Tcl's access to time information. + * + * Results: + * None. + * + * Side effects: + * Remembers the handlers, alters core behaviour. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_SetTimeProc( + Tcl_GetTimeProc *getProc, + Tcl_ScaleTimeProc *scaleProc, + void *clientData) +{ + tclGetTimeProcPtr = getProc; + tclScaleTimeProcPtr = scaleProc; + tclTimeClientData = clientData; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_QueryTimeProc -- + * + * TIP #233 (Virtualized Time): Query which time handlers are registered. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +Tcl_QueryTimeProc( + Tcl_GetTimeProc **getProc, + Tcl_ScaleTimeProc **scaleProc, + void **clientData) +{ + if (getProc) { + *getProc = tclGetTimeProcPtr; + } + if (scaleProc) { + *scaleProc = tclScaleTimeProcPtr; + } + if (clientData) { + *clientData = tclTimeClientData; + } +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/vendor/tcl/win/tclooConfig.sh b/vendor/tcl/win/tclooConfig.sh new file mode 100644 index 00000000..a400b5b7 --- /dev/null +++ b/vendor/tcl/win/tclooConfig.sh @@ -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 diff --git a/vendor/tcl/win/tclsh.exe.manifest.in b/vendor/tcl/win/tclsh.exe.manifest.in new file mode 100644 index 00000000..dd8a7c5f --- /dev/null +++ b/vendor/tcl/win/tclsh.exe.manifest.in @@ -0,0 +1,51 @@ + + + + Tcl command line shell (tclsh) + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/vendor/tcl/win/tclsh.ico b/vendor/tcl/win/tclsh.ico new file mode 100644 index 00000000..e2543187 Binary files /dev/null and b/vendor/tcl/win/tclsh.ico differ diff --git a/vendor/tcl/win/tclsh.rc b/vendor/tcl/win/tclsh.rc new file mode 100644 index 00000000..77d2d733 --- /dev/null +++ b/vendor/tcl/win/tclsh.rc @@ -0,0 +1,75 @@ +// +// Version Resource Script +// + +#include +#include + +// +// build-up the name suffix that defines the type of build this is. +// +#if STATIC_BUILD +#define SUFFIX_STATIC "s" +#else +#define SUFFIX_STATIC "" +#endif + +#if DEBUG && !UNCHECKED +#define SUFFIX_DEBUG "g" +#else +#define SUFFIX_DEBUG "" +#endif + +#define SUFFIX SUFFIX_STATIC 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_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "Tclsh Application\0" + VALUE "OriginalFilename", "tclsh" STRINGIFY(TCL_MAJOR_VERSION) STRINGIFY(TCL_MINOR_VERSION) SUFFIX ".exe\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 + +// +// Icon +// + +tclsh ICON DISCARDABLE "tclsh.ico" + +// +// This is needed for Windows 8.1 onwards. +// + +#ifndef RT_MANIFEST +#define RT_MANIFEST 24 +#endif +#ifndef CREATEPROCESS_MANIFEST_RESOURCE_ID +#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 +#endif +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "tclsh.exe.manifest" diff --git a/vendor/tcl/win/tcltest.rc b/vendor/tcl/win/tcltest.rc new file mode 100644 index 00000000..62852cdf --- /dev/null +++ b/vendor/tcl/win/tcltest.rc @@ -0,0 +1,75 @@ +// +// Version Resource Script +// + +#include +#include + +// +// build-up the name suffix that defines the type of build this is. +// +#if STATIC_BUILD +#define SUFFIX_STATIC "s" +#else +#define SUFFIX_STATIC "" +#endif + +#if DEBUG && !UNCHECKED +#define SUFFIX_DEBUG "g" +#else +#define SUFFIX_DEBUG "" +#endif + +#define SUFFIX SUFFIX_STATIC 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_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "Tcltest Application\0" + VALUE "OriginalFilename", "tcltest" STRINGIFY(TCL_MAJOR_VERSION) STRINGIFY(TCL_MINOR_VERSION) SUFFIX ".exe\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 + +// +// Icon +// + +tclsh ICON DISCARDABLE "tclsh.ico" + +// +// This is needed for Windows 8.1 onwards. +// + +#ifndef RT_MANIFEST +#define RT_MANIFEST 24 +#endif +#ifndef CREATEPROCESS_MANIFEST_RESOURCE_ID +#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 +#endif +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "tclsh.exe.manifest" diff --git a/vendor/tcl/win/vctool.bat b/vendor/tcl/win/vctool.bat new file mode 100755 index 00000000..964f1e26 --- /dev/null +++ b/vendor/tcl/win/vctool.bat @@ -0,0 +1,180 @@ +@echo off +REM Pass /? as argument for usage. + +IF DEFINED VCINSTALLDIR goto setup + +echo "Not in a Visual Studio command prompt." +exit /B 1 + +:setup +setlocal + +set "tool=%0" + +REM Get the current directory +set "currentDir=%CD%" + +REM Use FOR command to get the parent directory +for %%I in ("%currentDir%") do set "parentDir=%%~dpI" + +REM Remove the trailing backslash +set "parentDir=%parentDir:~0,-1%" + +REM Use FOR command again to get the parent of the parent directory +for %%J in ("%parentDir%") do set "grandParentDir=%%~dpJ" + +REM Remove the trailing backslash +set "grandParentDir=%grandParentDir:~0,-1%" + +REM Use FOR command to extract the last component +for %%I in ("%grandParentDir%") do set "grandParentTail=%%~nxI" + +REM Extract the drive letter +for %%I in ("%currentDir%") do set "driveLetter=%%~dI" + +set ARCH=%VSCMD_ARG_TGT_ARCH% +if "%TCLINSTALLROOT%" == "" ( + set INSTROOT=%driveLetter%\Tcl\%grandParentTail%\%ARCH% +) else ( + set INSTROOT=%TCLINSTALLROOT%\%grandParentTail%\%ARCH% +) + +REM Parse options +:options +if "%1" == "" goto dobuilds +if "%1" == "/?" goto help +if "%1" == "-?" goto help +if /i "%1" == "/help" goto help +if "%1" == "all" goto all +if "%1" == "shared" goto shared +if "%1" == "static" goto static +if "%1" == "shared_noembed" goto shared_noembed +if "%1" == "static_noembed" goto static_noembed +if "%1" == "compile" goto compile +if "%1" == "test" goto targets +if "%1" == "install" goto targets +if "%1" == "runshell" goto targets +if "%1" == "debug" goto debug +if "%~x1" == ".test" goto testpat +goto help + +:debug +set debug=1 +shift +goto options + +:shared +set shared=1 +shift +goto options + +:static +set static=1 +shift +goto options + +:shared_noembed +set shared_noembed=1 +shift +goto options + +:static_noembed +set static_noembed=1 +shift +goto options + +:all +set shared=1 +set static=1 +set shared_noembed=1 +set static_noembed=1 +shift +goto options + +:targets +set TARGETS=%TARGETS% %1 +shift +goto options + +:testpat +set TESTPAT=%1 +shift +goto options + +REM The makefile.vc compilation target is called "release" +:compile +set TARGETS=%TARGETS% release +shift +goto options + +:dobuilds +if "%shared%%static%%shared_noembed%%static_noembed%" == "" ( + echo At least one of shared, static, shared_noembed, static_noembed, all must be specified. + echo For more help, type "%0 help" + goto error +) + +if DEFINED shared ( + call :runmake shared +) +if DEFINED shared_noembed ( +call :runmake shared-noembed noembed +) +if DEFINED static ( +call :runmake static static +) +if DEFINED static_noembed ( +call :runmake static-noembed "static,noembed" +) + +:done +endlocal +exit /b 0 + +:error +endlocal +exit /b 1 + +:: call :runmake dir opts +:runmake +if "%debug%" == "" ( + nmake /s /f makefile.vc OUT_DIR=%currentDir%\vc-%ARCH%-%1 TMP_DIR=%currentDir%\vc-%ARCH%-%1\objs OPTS=pdbs,%2 INSTALLDIR=%INSTROOT%-%1 %TARGETS% && goto error +) else ( + nmake /s /f makefile.vc OUT_DIR=%currentDir%\vc-%ARCH%-%1-debug TMP_DIR=%currentDir%\vc-%ARCH%-%1-debug\objs OPTS=pdbs,%2 cdebug="-Zi -Od" INSTALLDIR=%INSTROOT%-%1-debug %TARGETS% && goto error +) +goto eof + +:help +echo. +echo Usage: %0 arg ... +echo. +echo where each arg may be either a build config or a target or "debug". +echo. +echo Configs: shared, static, shared_noembed, static_noembed, all +echo Targets: compile (default), test, install +echo. +echo Multiple configs and targets may be specified and intermixed. +echo At least one config must be specified. If no targets specified, +echo default is compile. If multiple targets are present, they +echo are built in specified order. +echo. +echo If "debug" is supplied as an argument, the build has optimizations +echo disabled and full debug information. +echo. +echo If environment variable TCLINSTALLROOT is defined, install target +echo will be subdirectory under it named after the grandparent of the +echo current directory. TCLINSTALLROOT defaults to the X:\Tcl where +echo X is the current drive. +echo. +echo For example, if the current directory C:\src\core-8-branch\tcl\win, +echo install directories will be echo under c:\Tcl\core-8-branch\ as +echo x64-shared, x64-static etc. or x64-shared-debug etc. if "debug" passed. +echo. +echo Examples: +echo %tool% shared (Builds default "shared" config) +echo %tool% shared test (Tests shared build) +echo %tool% static compile test (Builds and tests static build) +echo %tool% all debug (Build debug versions of all configs) +echo %tool% all compile install debug (Builds and installs all configs) +echo %tool% shared static shared_noembed (Builds three configs) +goto done diff --git a/vendor/tcl/win/x86_64-w64-mingw32-nmakehlp.exe b/vendor/tcl/win/x86_64-w64-mingw32-nmakehlp.exe new file mode 100755 index 00000000..f821addd Binary files /dev/null and b/vendor/tcl/win/x86_64-w64-mingw32-nmakehlp.exe differ