From 050539759028356c8411d5b9ff9de84f279bcae2 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Wed, 8 Jul 2026 19:23:40 +1000 Subject: [PATCH] G-037 achieved: make.tcl vfslibs propagates vendorlib packages into kit vfs lib_tcl trees; 8.6 kits to tcludp 1.0.13 New 'make.tcl vfslibs' step (also run inside 'make.tcl project' before kit vfs assembly): propagates declared vendored platform-library packages into kit vfs lib_tcl trees, ending the hand-copying that let kits ship a known-buggy tcludp through three make invocations (the G-036/G-037 motivating case). - declaration: src/runtime/vendorlib_vfs.toml, parsed with bootsupport tomlish (no ad-hoc parsing; G-024's mapvfs toml conversion NOT required first - this file is recorded as a candidate for absorption into it). [install.] entries: source (relative to src/), targets (kit vfs lib folders relative to src/vfs/), optional supersedes (folder names removed from each target before install - explicit, no silent mixed-version provision per G-035 concerns). - participation is per-package per-kit; authority is the declaration - nothing syncs undeclared, so the known vendorlib/kit twapi drift is deliberately untouched (resolves the direction/authority design question). - punkcheck provenance at src/vfs/.punkcheck (gitignored, outside kit payloads); skip-if-unchanged proven (rerun: 0 copied, 9 skipped per target); malformed/unresolvable entries fail the build naming the entry (exit 3). - 8.6 kits upgraded (the G-036 loose-end decision, recorded): tcludp 1.0.13 is dual-generation (pkgIndex selects tcl9udp1013.dll on 9+, udp1013t.dll on 8.5+) so the single vendorlib_tcl9 source serves punk8win.vfs/lib_tcl8; superseded udp1.0.12 removed. - verified on rebuilt kits: punksys/punkbi (8.6.13) and punk902z (9.0.2) all report package require udp == 1.0.13 loading the generation-appropriate dll from their vfs; no udp1.0.12 remains under src/vfs; punksys piped-shell exit smoke clean. - G-037 flipped proposed -> achieved 2026-07-08 under user direction (no intermediate active stop). - includes project-build byproducts via established channels: bootsupport snapshots refreshed to punk-0.2.1 / punk::lib-0.3.0 (from today's version bumps) and make.tcl synced into layout copies. - project 0.4.8 (CHANGELOG entry) Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- CHANGELOG.md | 5 + GOALS.md | 2 +- goals/G-036-tcl9-udp-console-worker-wedge.md | 12 +- goals/G-037-vendorlib-vfs-propagation.md | 47 ++- punkproject.toml | 2 +- .../modules/{punk-0.2.0.tm => punk-0.2.1.tm} | 19 +- .../modules/punk/lib-0.3.0.tm} | 56 ++- src/make.tcl | 99 +++++- .../custom/_project/punk.basic/src/make.tcl | 99 +++++- .../src/bootsupport/modules/punk-0.2.1.tm} | 19 +- .../bootsupport/modules/punk/lib-0.3.0.tm} | 56 ++- .../_project/punk.project-0.1/src/make.tcl | 99 +++++- .../src/bootsupport/modules/punk-0.2.1.tm} | 19 +- .../bootsupport/modules/punk/lib-0.3.0.tm} | 56 ++- .../_project/punk.shell-0.1/src/make.tcl | 99 +++++- src/runtime/vendorlib_vfs.toml | 32 ++ .../lib_tcl8/tcludp1.0.13/demos/broadcast.tcl | 39 +++ .../tcludp1.0.13/demos/bug1158628.tcl | 56 +++ .../lib_tcl8/tcludp1.0.13/demos/chat.tcl | 81 +++++ .../lib_tcl8/tcludp1.0.13/demos/multicast.tcl | 45 +++ .../lib_tcl8/tcludp1.0.13/demos/udpcat.tcl | 65 ++++ .../lib_tcl8/tcludp1.0.13/pkgIndex.tcl | 33 ++ .../lib_tcl8/tcludp1.0.13/tcl9udp1013.dll | Bin 0 -> 31744 bytes .../lib_tcl8/tcludp1.0.13/udp.html | 326 ++++++++++++++++++ .../lib_tcl8/tcludp1.0.13/udp1013t.dll | Bin 0 -> 30720 bytes .../lib_tcl8/udp1.0.12/pkgIndex.tcl | 2 - .../lib_tcl8/udp1.0.12/udp1012.dll | Bin 54784 -> 0 bytes 27 files changed, 1325 insertions(+), 43 deletions(-) rename src/bootsupport/modules/{punk-0.2.0.tm => punk-0.2.1.tm} (99%) rename src/{project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.2.1.tm => bootsupport/modules/punk/lib-0.3.0.tm} (99%) rename src/project_layouts/custom/_project/{punk.shell-0.1/src/bootsupport/modules/punk-0.2.0.tm => punk.project-0.1/src/bootsupport/modules/punk-0.2.1.tm} (99%) rename src/{bootsupport/modules/punk/lib-0.2.1.tm => project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.3.0.tm} (99%) rename src/project_layouts/custom/_project/{punk.project-0.1/src/bootsupport/modules/punk-0.2.0.tm => punk.shell-0.1/src/bootsupport/modules/punk-0.2.1.tm} (99%) rename src/project_layouts/custom/_project/{punk.project-0.1/src/bootsupport/modules/punk/lib-0.2.1.tm => punk.shell-0.1/src/bootsupport/modules/punk/lib-0.3.0.tm} (99%) create mode 100644 src/runtime/vendorlib_vfs.toml create mode 100644 src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/broadcast.tcl create mode 100644 src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/bug1158628.tcl create mode 100644 src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/chat.tcl create mode 100644 src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/multicast.tcl create mode 100644 src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/udpcat.tcl create mode 100644 src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/pkgIndex.tcl create mode 100644 src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/tcl9udp1013.dll create mode 100644 src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/udp.html create mode 100644 src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/udp1013t.dll delete mode 100644 src/vfs/punk8win.vfs/lib_tcl8/udp1.0.12/pkgIndex.tcl delete mode 100644 src/vfs/punk8win.vfs/lib_tcl8/udp1.0.12/udp1012.dll diff --git a/CHANGELOG.md b/CHANGELOG.md index 57e6b59b..35caab7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` "Project Versioning" section for the bump policy. +## [0.4.8] - 2026-07-08 + +- G-037 completed (flipped to achieved): new `make.tcl vfslibs` step (also run inside `make.tcl project`) propagates declared vendored platform-library packages into kit vfs lib_tcl trees — declarations in `src/runtime/vendorlib_vfs.toml` (tomlish-parsed, per-package per-kit with explicit `supersedes` removal so no silent mixed-version provision; punkcheck provenance at src/vfs/.punkcheck, outside kit payloads; malformed entries fail the build naming the entry). G-024's mapvfs toml conversion was not required; the file is recorded as a candidate for absorption into it. Authority is the declaration — undeclared drift (e.g. the vendorlib/kit twapi divergence) is deliberately untouched. +- 8.6 kits upgraded to tcludp 1.0.13 via the new step (the G-036 loose-end decision): the 1.0.13 folder is dual-generation (pkgIndex selects tcl9udp1013.dll or udp1013t.dll), so the single vendorlib_tcl9 source serves punk8win.vfs/lib_tcl8, replacing udp 1.0.12. Verified on rebuilt kits: punksys/punkbi (8.6.13) and punk902z (9.0.2) all load udp 1.0.13 from their vfs; no udp1.0.12 remains under src/vfs. + ## [0.4.7] - 2026-07-08 - G-036 completed (flipped to achieved): `punk::lib` 0.3.0 adds `punk::lib::check::has_libbug_udp_threadexit` — version-based detection of the tcludp < 1.0.13 thread-exit bug on Tcl 9 Windows (the per-thread exit handler closes process-global event handles, silently freezing every other udp-using thread's event loop after any udp-loaded thread exits; root-caused and fixed-by-upgrade earlier in 0.4.3). `has_libbug_*` is the new check family for bundled/vendored library bugs; `help tcl` (punk 0.2.1) now scans it alongside `has_tclbug_*`, supports a full `url` reference key for non tcl-core trackers, and no longer errors on a triggered check carrying a reference without a description. Detection is loaded-version based, falling back to the best available registered version without loading the binary. New checkbugs.test covers the classifier matrix, the live check's dict shape/consistency, and a contract test over all existing check procs. Open decisions recorded in the G-036 detail: punk8win (8.6) kit's udp 1.0.12 swap; optional upstream tickets for residual tcludp trunk weaknesses. diff --git a/GOALS.md b/GOALS.md index d1dffec0..c9d0ec4b 100644 --- a/GOALS.md +++ b/GOALS.md @@ -267,7 +267,7 @@ Detail: goals/G-036-tcl9-udp-console-worker-wedge.md Goal: the Tcl 9-only wedge - a worker thread that has used a tcludp syslog socket stops servicing its event queue (timers, thread::send) when the punkshell process is console-attached, the proximate trigger of the piped-stdin exit/quit hang - is root-caused to a named component (Tcl 9 Windows console driver, tcludp, Thread extension, or a specific interaction) with the smallest demonstrating repro, so the user can decide on and manually file an upstream report. Acceptance: (reworked 2026-07-08 after the root cause was found) the wedge mechanism is identified and written up in the detail file - DONE: bundled tcludp 1.0.12's Windows per-thread UDP_ExitProc closes the process-global synchronization events, proven by dump handle-table analysis plus a live CloseHandle breakpoint and confirmed by the upstream 1.0.12->1.0.13 diff, which already fixes it (so no upstream report is required; the standalone minimal repro originally required here is waived as moot by the user); the tcl9 kits bundle tcludp >= 1.0.13 with the in-context batch harness baseline resolved - DONE 2026-07-08 (run-2 syslog workers alive vs the 4/4-wedged 1.0.12 baseline); DONE 2026-07-08 (punk::lib 0.3.0 has_libbug_udp_threadexit, surfaced via 'help tcl' in punk 0.2.1): a has_bug-style detection in the punkshell check machinery (in the vein of punk::lib::check::has_tclbug_* / punk::console::check::has_bug_*, surfaced through the same reporting as 'help tcl'/'help console') reports the vulnerable combination - simple version-based detection (loaded/bundled tcludp < 1.0.13 on Tcl 9 Windows) is sufficient, no behavioural probe needed; loose-end decisions (punk8win 8.6 kit's udp 1.0.12 swap; optional upstream tickets for residual tcludp trunk weaknesses) recorded in the detail file when made (open as of 2026-07-08 - non-gating). -### G-037 [proposed] Propagate platform vendor libraries into kit vfs lib_tcl trees via make.tcl +### G-037 [achieved 2026-07-08] Propagate platform vendor libraries into kit vfs lib_tcl trees via make.tcl Scope: src/make.tcl (new or extended step), src/vendorlib_tcl8 + src/vendorlib_tcl9 (sources), src/vfs/.vfs/lib_tcl8 + lib_tcl9 (targets), punkcheck tracking Detail: goals/G-037-vendorlib-vfs-propagation.md Goal: platform-specific vendored binary packages under src/vendorlib_tcl/ reach the kit vfs lib_tcl trees through a make.tcl step instead of hand-copying - updating a vendored package becomes a vendorlib drop plus standard build invocations (motivating case 2026-07-08: tcludp 1.0.12 -> 1.0.13 for the G-036 wedge fix - `libs`, `vfscommonupdate` and `project` all completed while every kit vfs still bundled udp 1.0.12, requiring a manual copy into each vfs lib_tcl9 folder). diff --git a/goals/G-036-tcl9-udp-console-worker-wedge.md b/goals/G-036-tcl9-udp-console-worker-wedge.md index 896a6a79..16d73248 100644 --- a/goals/G-036-tcl9-udp-console-worker-wedge.md +++ b/goals/G-036-tcl9-udp-console-worker-wedge.md @@ -301,9 +301,15 @@ shorthand). Verified: current punk902z kit (udp 1.0.13) reports bug=0 with no wa a simulated triggered has_libbug_* check renders the warning block with description and url; checkbugs.test also pins the buginfo dict contract across all existing check procs. -Loose ends / optional follow-ups (open as of 2026-07-08 - non-gating, record decisions here when made): -- Check which tcludp the punksys (8.6) kit bundles and why 8.6 appeared immune (different udp - version, different exit-handler ordering, or fast-fail luck). +Loose ends / optional follow-ups (record decisions here when made): +- DECIDED + DONE 2026-07-08: punk8win.vfs (8.6 kits punksys/punkbi) swapped from udp 1.0.12 + to tcludp 1.0.13 - the 1.0.13 folder is dual-generation (udp1013t.dll for 8.x selected by + its pkgIndex) so the same vendorlib_tcl9 source serves lib_tcl8. Applied via the new + G-037 vfslibs propagation step (declaration in src/runtime/vendorlib_vfs.toml removes the + superseded udp1.0.12). The 8.6 apparent immunity remains unexplained but is now moot for + punkshell kits (same fixed code everywhere). +- Check why 8.6 appeared immune (different exit-handler ordering, or fast-fail luck) - + academic now that all kits carry 1.0.13. - Residual weaknesses still present in tcludp trunk, candidates for polite upstream tickets: auto-reset-event-used-as-lock (lost-SetEvent risk, no ownership); packetNum not decremented on the recvfrom error path (ICMP port-unreachable leaves packetNum>0 -> permanent diff --git a/goals/G-037-vendorlib-vfs-propagation.md b/goals/G-037-vendorlib-vfs-propagation.md index 72ad6cb3..c5baf621 100644 --- a/goals/G-037-vendorlib-vfs-propagation.md +++ b/goals/G-037-vendorlib-vfs-propagation.md @@ -1,6 +1,6 @@ # G-037 Propagate platform vendor libraries into kit vfs lib_tcl trees via make.tcl -Status: proposed +Status: achieved 2026-07-08 Scope: src/make.tcl (new or extended step), src/vendorlib_tcl8 + src/vendorlib_tcl9 (sources), src/vfs/.vfs/lib_tcl8 + lib_tcl9 (targets), punkcheck tracking Acceptance: with a newer package version placed under src/vendorlib_tcl9/, one documented make.tcl invocation updates the participating src/vfs/*/lib_tcl9 trees - installing the new package and removing or explicitly retiring the superseded version (no silent mixed-version provision, per the G-035 concerns) - with punkcheck-tracked provenance; which vfs folders participate is explicitly declared per kit rather than blanket-copied (kit vfs package sets may intentionally differ), with the declaration mechanism recorded (candidate home: the G-024 mapvfs toml); a subsequent `make.tcl project` yields kits loading the new version (provable via the tcludp case: built punk902z reports `package require udp` == 1.0.13 with no udp1.0.12 folder remaining in its vfs); the lib_tcl8 tree gets the same treatment or an explicit exclusion rationale in the goal record. @@ -47,6 +47,51 @@ What the existing targets actually cover (verified in make.tcl at the time of wr - **lib_tcl8**: same mechanism should serve the tcl8 kits (punk8win.vfs still bundles udp 1.0.12 as of 0.4.3 - see G-036 loose ends), or the exclusion is recorded here. +## Implementation (2026-07-08) + +Mechanism as landed (design points above settled as follows): + +- **Declaration**: `src/runtime/vendorlib_vfs.toml`, parsed with the bootsupport tomlish + package (1.1.10) - no ad-hoc parsing. Named tables `[install.]` with keys: + `source` (package folder relative to src/), `targets` (list of kit vfs lib folders + relative to src/vfs/), optional `supersedes` (folder names removed from each target + before install). G-024's mapvfs toml conversion was NOT required first - this file is + recorded as a candidate for absorption into it. (make.tcl's own help already carried a + matching intent: "update the src/vendorlib based on src/vendorlib/config.toml (todo)".) +- **Step**: `make.tcl vfslibs` (new subcommand), also run automatically inside + `make.tcl project` before kit vfs assembly. Malformed/unresolvable entries fail loudly + naming the entry (exit 3). +- **Supersession**: explicit `supersedes` folder-name list per entry (the simpler option; + pkgIndex-declared package identity detection remains a possible refinement). The step + never removes the folder it is about to install, and removal happens before install. +- **Direction/authority**: declaration-driven only - nothing syncs unless declared, so the + observed twapi drift (vendorlib 5.0b1 older than kit copies) is untouched until someone + declares a twapi entry. This resolves the "explicit direction/authority semantics" + question: authority is the declaration, not file timestamps/versions. +- **Cross-generation sources**: allowed when the package supports it - the motivating + tcludp 1.0.13 folder is dual-generation (tcl9udp1013.dll + udp1013t.dll, pkgIndex + selects by [package vsatisfies]), so the single vendorlib_tcl9 source serves the + punk8win.vfs/lib_tcl8 target (no duplicate binary committed under vendorlib_tcl8). +- **punkcheck provenance**: records at `src/vfs/.punkcheck` via -punkcheck-folder (kept + outside the .vfs payload folders so no records are baked into kits); skip-if-unchanged + verified (second run: 0 copied, 9 skipped per target). +- **lib_tcl8**: same mechanism, exercised immediately - punk8win.vfs/lib_tcl8 had + udp1.0.12 removed and tcludp1.0.13 installed via the declaration (the G-036 loose-end + decision, made 2026-07-08). + +Verification (2026-07-08, acceptance met - goal flipped to achieved): + +- `make.tcl vfslibs`: installed tcludp1.0.13 into punk8win.vfs/lib_tcl8 (9 files), + removed superseded udp1.0.12; re-recorded the two tcl9 kit targets (previously + hand-copied). Second run: 0 copied / 9 skipped per target (punkcheck skip-unchanged). +- `make.tcl project` (with the vfslibs step now inside it): INSTALLED KITS punkbi.exe, + punksys.exe, punk902z.exe. punk9win_for_tkruntime.vfs correctly detected as unchanged + (its 1.0.13 payload predated the step) - cached kit redeployed. +- Built kits: punksys 8.6.13 `package require udp` = 1.0.13 loading + lib_tcl8/tcludp1.0.13/udp1013t.dll; punkbi likewise; punk902z 9.0.2 = 1.0.13 loading + zipfs lib_tcl9/tcludp1.0.13/tcl9udp1013.dll. No udp1.0.12 folder remains anywhere + under src/vfs. Piped-shell exit smoke on punksys clean (PUNK_PIPE_EOF=exit). + ## Alternatives considered - Extending `vfscommonupdate` to also sync platform libs into `_vfscommon.vfs` - rejected diff --git a/punkproject.toml b/punkproject.toml index ae1578fd..8b646556 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,3 +1,3 @@ [project] name = "punkshell" -version = "0.4.7" +version = "0.4.8" diff --git a/src/bootsupport/modules/punk-0.2.0.tm b/src/bootsupport/modules/punk-0.2.1.tm similarity index 99% rename from src/bootsupport/modules/punk-0.2.0.tm rename to src/bootsupport/modules/punk-0.2.1.tm index a8086722..a167b7f4 100644 --- a/src/bootsupport/modules/punk-0.2.0.tm +++ b/src/bootsupport/modules/punk-0.2.1.tm @@ -8570,7 +8570,7 @@ namespace eval punk { punk::args::define { @id -id ::punk::helptopic::tcl - @cmd -name "help tcl" -summary "Tcl version warnings." -help "Show the running Tcl patchlevel and build-info, with warnings for known Tcl bugs affecting this interpreter (as detected by the punk::lib::check::has_tclbug_* checks)." + @cmd -name "help tcl" -summary "Tcl version warnings." -help "Show the running Tcl patchlevel and build-info, with warnings for known Tcl bugs affecting this interpreter and known bugs in bundled library packages (as detected by the punk::lib::check::has_tclbug_* and has_libbug_* checks)." @values -min 0 -max 0 } proc tcl {context args} { @@ -8580,8 +8580,12 @@ namespace eval punk { catch { append text \n "Tcl build-info: [::tcl::build-info]" } - #generate warningblocks for each triggered Tcl bug in namespace ::punk::lib::check - set bugcheck_procs [info procs ::punk::lib::check::has_tclbug*] + #generate warningblocks for each triggered bug check in namespace ::punk::lib::check + #has_tclbug_* = Tcl core bugs; has_libbug_* = bundled/vendored library bugs (e.g. tcludp - G-036) + set bugcheck_procs [concat\ + [info procs ::punk::lib::check::has_tclbug*]\ + [info procs ::punk::lib::check::has_libbug*]\ + ] foreach bp $bugcheck_procs { set buginfo [$bp] if {[dict get $buginfo bug]} { @@ -8595,12 +8599,15 @@ namespace eval punk { major {set highlight [punk::ansi::a+ red bold]} default {set highlight ""} } + set indent " " append warningblock \n $highlight "warning level: $level $bp triggered." if {[dict exists $buginfo description]} { - set indent " " append warningblock \n "[punk::lib::indent [dict get $buginfo description] $indent]" } - if {[dict exists $buginfo bugref] && [dict get $buginfo bugref] ne ""} { + if {[dict exists $buginfo url] && [dict get $buginfo url] ne ""} { + #full reference url (e.g. non tcl-core trackers such as tcludp) + append warningblock \n "${indent}see [punk::ansi::hyperlink [dict get $buginfo url]]" + } elseif {[dict exists $buginfo bugref] && [dict get $buginfo bugref] ne ""} { set bugref [dict get $buginfo bugref] append warningblock \n "${indent}see [punk::ansi::hyperlink https://core.tcl-lang.org/tcl/tktview/$bugref]" } @@ -9350,7 +9357,7 @@ punkcheck::cli set_alias punkcheck package provide punk [namespace eval punk { #FUNCTL variable version - set version 0.2.0 + set version 0.2.1 }] diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.2.1.tm b/src/bootsupport/modules/punk/lib-0.3.0.tm similarity index 99% rename from src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.2.1.tm rename to src/bootsupport/modules/punk/lib-0.3.0.tm index 27aa0795..09da6c9a 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.2.1.tm +++ b/src/bootsupport/modules/punk/lib-0.3.0.tm @@ -8,7 +8,7 @@ # (C) 2024 # # @@ Meta Begin -# Application punk::lib 0.2.1 +# Application punk::lib 0.3.0 # Meta platform tcl # Meta license BSD # @@ Meta End @@ -18,7 +18,7 @@ # doctools header # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ #*** !doctools -#[manpage_begin punkshell_module_punk::lib 0 0.2.1] +#[manpage_begin punkshell_module_punk::lib 0 0.3.0] #[copyright "2024"] #[titledesc {punk general utility functions}] [comment {-- Name section and table of contents description --}] #[moddesc {punk library}] [comment {-- Description at end of page heading --}] @@ -250,6 +250,56 @@ tcl::namespace::eval punk::lib::check { return [dict create bug $bug bugref e38dc74e2 description $description level medium] } + #has_libbug_* procs report bugs in bundled/vendored library packages rather than the Tcl core. + #They are surfaced through the same 'help tcl' warning report as the has_tclbug_* checks. + + #pure classifier, separated for testability - facts in, verdict out + proc libbug_udp_threadexit_applies {udpversion platform tclversion} { + if {$udpversion eq ""} { + return 0 ;#no udp package known - nothing vulnerable to load + } + if {$platform ne "windows"} { + return 0 + } + if {![package vsatisfies $tclversion 9]} { + #tcl 8.6 exhibited apparent immunity in the G-036 investigation (unexplained - see goal detail) + #detection deliberately scoped to Tcl 9 per G-036 acceptance + return 0 + } + return [expr {[package vcompare $udpversion 1.0.13] < 0}] + } + proc has_libbug_udp_threadexit {} { + #tcludp <= 1.0.12 on Windows: the per-thread exit handler UDP_ExitProc closes the + #PROCESS-GLOBAL sockListLock/waitForSock event handles. The first udp-loaded thread to + #exit corrupts the lock for every other udp-loaded thread - their event loops block + #forever in WaitForSingleObject on a dead or recycled handle (timers and thread::send + #unserviced). In punkshell the visible casualty is syslog log workers silently freezing + #(the original exit/quit hang this caused is separately mitigated). Root-caused + #2026-07-08 - see goal G-036 detail. Fixed upstream in tcludp 1.0.13 (per-thread + #UdpThreadExitProc deletes only the event source; ExitSockets closes the events at + #process exit). + #Version-based detection only (per G-036 acceptance) - no behavioural probe. + set udpversion [package provide udp] + if {$udpversion eq ""} { + #not loaded - determine what version would be provided, without loading the binary: + #an unsatisfiable require triggers the package unknown scan (registering ifneeded + #scripts) then fails before any dll load. + catch {package require udp 999999} + set available [package versions udp] + if {[llength $available]} { + set udpversion [lindex [lsort -command {package vcompare} $available] end] + } + } + set bug [libbug_udp_threadexit_applies $udpversion $::tcl_platform(platform) [package provide Tcl]] + if {$udpversion eq ""} { + set verinfo "no udp package found" + } else { + set verinfo "udp $udpversion" + } + set description "tcludp < 1.0.13 on Tcl 9 Windows ($verinfo): the udp package's per-thread exit\nhandler closes process-global event handles - after any udp-using thread exits, every\nother udp-using thread's event loop can freeze silently (e.g. syslog log workers stop).\nUpgrade the bundled/available udp package to 1.0.13+ (see goal G-036)." + return [dict create bug $bug bugref "" url https://core.tcl-lang.org/tcludp description $description level medium libversion $udpversion] + } + proc has_tclbug_safeinterp_compile {{show 0}} { #ensemble calls within safe interp not compiled #https://core.tcl-lang.org/tcl/tktview/1095bf7f756f9aed6bde @@ -9069,7 +9119,7 @@ namespace eval ::punk::args::register { package provide punk::lib [tcl::namespace::eval punk::lib { variable pkg punk::lib variable version - set version 0.2.1 + set version 0.3.0 }] return diff --git a/src/make.tcl b/src/make.tcl index 8600aae7..eead4196 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -22,7 +22,7 @@ namespace eval ::punkboot { variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list] variable non_help_flags [list -k] variable help_flags [list -help --help /? -h] - variable known_commands [list project modules libs packages vfs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion] + variable known_commands [list project modules libs packages vfs vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion] } @@ -1348,6 +1348,10 @@ proc ::punkboot::punkboot_gethelp {args} { append h " $scriptname vendorupdate" \n append h " - update the src/vendormodules based on src/vendormodules/include_modules.config" \n \n append h " - update the src/vendorlib based on src/vendorlib/config.toml (todo)" \n \n + append h " $scriptname vfslibs" \n + append h " - propagate declared vendored platform-library packages into kit vfs lib_tcl trees" \n + append h " - declarations (per-package per-kit, with superseded-version removal) live in src/runtime/vendorlib_vfs.toml" \n + append h " - also runs automatically as part of '$scriptname project'" \n \n append h " $scriptname vfscommonupdate" \n append h " - update the src/vfs/_vfscommon.vfs from compiled src/modules and src/lib etc" \n append h " - before calling this (followed by make project) - you can test using '(.exe) dev'" \n @@ -2353,7 +2357,7 @@ if {$::punkboot::command eq "bootsupport"} { -if {$::punkboot::command ni {project modules libs packages vfs bin}} { +if {$::punkboot::command ni {project modules libs packages vfs vfslibs bin}} { puts stderr "Command $::punkboot::command not implemented - aborting." flush stderr after 100 @@ -2475,6 +2479,97 @@ if {$::punkboot::command in {project packages libs}} { } } +if {$::punkboot::command in {project vfslibs}} { + #G-037: propagate declared vendored platform-library packages into kit vfs lib_tcl trees. + #Participation is per-package per-kit (kit vfs package sets intentionally differ - e.g. twapi + #versions vary per kit) - declared in src/runtime/vendorlib_vfs.toml, never blanket-copied. + #punkcheck records live at src/vfs/.punkcheck so they are not baked into kit payloads. + set vfslibs_config $sourcefolder/runtime/vendorlib_vfs.toml + if {![file exists $vfslibs_config]} { + puts stdout "VFSLIBS: no config at $vfslibs_config - skipping vendorlib -> kit vfs propagation" + } else { + if {[catch {package require tomlish} tomlish_err]} { + puts stderr "$A(BAD)VFSLIBS: cannot process $vfslibs_config - tomlish package unavailable ($tomlish_err)$A(RST)" + exit 3 + } + set fd [open $vfslibs_config r] + fconfigure $fd -encoding utf-8 + set vfslibs_tomldata [read $fd] + close $fd + if {[catch {tomlish::to_dict [tomlish::from_toml $vfslibs_tomldata]} vfslibs_dict]} { + puts stderr "$A(BAD)VFSLIBS: failed to parse $vfslibs_config\n$vfslibs_dict$A(RST)" + exit 3 + } + #tomlish::to_dict values are type-tagged (e.g {type STRING value x}, {type ARRAY value {...}}) + proc ::punkboot::lib::toml_untag {tagged} { + if {[dict get $tagged type] eq "ARRAY"} { + set plain [list] + foreach el [dict get $tagged value] { + lappend plain [::punkboot::lib::toml_untag $el] + } + return $plain + } + return [dict get $tagged value] + } + set vfslibs_installs [dict create] + if {[dict exists $vfslibs_dict install]} { + set vfslibs_installs [dict get $vfslibs_dict install] + } + if {![dict size $vfslibs_installs]} { + puts stdout "VFSLIBS: no \[install.\] entries found in $vfslibs_config" + } + dict for {iname ientry} $vfslibs_installs { + foreach reqkey {source targets} { + if {![dict exists $ientry $reqkey]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname in $vfslibs_config is missing required key '$reqkey'$A(RST)" + exit 3 + } + } + set isource [punkboot::lib::toml_untag [dict get $ientry source]] + set itargets [punkboot::lib::toml_untag [dict get $ientry targets]] + set isupersedes [list] + if {[dict exists $ientry supersedes]} { + set isupersedes [punkboot::lib::toml_untag [dict get $ientry supersedes]] + } + set source_pkg_folder $sourcefolder/$isource + if {![file isdirectory $source_pkg_folder]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$A(RST)" + exit 3 + } + set pkgtail [file tail $source_pkg_folder] + foreach t $itargets { + set target_vfs_dir [lindex [file split $t] 0] + if {![file isdirectory $sourcefolder/vfs/$target_vfs_dir]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname target '$t' - no vfs folder at $sourcefolder/vfs/$target_vfs_dir$A(RST)" + exit 3 + } + set target_lib_folder $sourcefolder/vfs/$t + file mkdir $target_lib_folder + #supersession first - no silent mixed-version provision (G-035 concerns) + foreach sup $isupersedes { + if {$sup eq $pkgtail} { + continue ;#never remove what we are about to install + } + set suppath $target_lib_folder/$sup + if {[file exists $suppath]} { + puts stdout "VFSLIBS install.$iname: removing superseded '$sup' from vfs/$t" + file delete -force $suppath + } + } + puts stdout "VFSLIBS install.$iname: $isource -> vfs/$t/$pkgtail (if source file changed)" + set resultdict [punkcheck::install $source_pkg_folder $target_lib_folder/$pkgtail\ + -installer make.tcl\ + -createdir 1\ + -overwrite installedsourcechanged-targets\ + -punkcheck-folder $sourcefolder/vfs\ + -progresschannel stdout\ + ] + puts stdout [punkcheck::summarize_install_resultdict $resultdict] + } + } + } +} + if {$::punkboot::command in {project packages libs}} { ######################################################## lappend projectlibfolders lib diff --git a/src/project_layouts/custom/_project/punk.basic/src/make.tcl b/src/project_layouts/custom/_project/punk.basic/src/make.tcl index 8600aae7..eead4196 100644 --- a/src/project_layouts/custom/_project/punk.basic/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.basic/src/make.tcl @@ -22,7 +22,7 @@ namespace eval ::punkboot { variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list] variable non_help_flags [list -k] variable help_flags [list -help --help /? -h] - variable known_commands [list project modules libs packages vfs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion] + variable known_commands [list project modules libs packages vfs vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion] } @@ -1348,6 +1348,10 @@ proc ::punkboot::punkboot_gethelp {args} { append h " $scriptname vendorupdate" \n append h " - update the src/vendormodules based on src/vendormodules/include_modules.config" \n \n append h " - update the src/vendorlib based on src/vendorlib/config.toml (todo)" \n \n + append h " $scriptname vfslibs" \n + append h " - propagate declared vendored platform-library packages into kit vfs lib_tcl trees" \n + append h " - declarations (per-package per-kit, with superseded-version removal) live in src/runtime/vendorlib_vfs.toml" \n + append h " - also runs automatically as part of '$scriptname project'" \n \n append h " $scriptname vfscommonupdate" \n append h " - update the src/vfs/_vfscommon.vfs from compiled src/modules and src/lib etc" \n append h " - before calling this (followed by make project) - you can test using '(.exe) dev'" \n @@ -2353,7 +2357,7 @@ if {$::punkboot::command eq "bootsupport"} { -if {$::punkboot::command ni {project modules libs packages vfs bin}} { +if {$::punkboot::command ni {project modules libs packages vfs vfslibs bin}} { puts stderr "Command $::punkboot::command not implemented - aborting." flush stderr after 100 @@ -2475,6 +2479,97 @@ if {$::punkboot::command in {project packages libs}} { } } +if {$::punkboot::command in {project vfslibs}} { + #G-037: propagate declared vendored platform-library packages into kit vfs lib_tcl trees. + #Participation is per-package per-kit (kit vfs package sets intentionally differ - e.g. twapi + #versions vary per kit) - declared in src/runtime/vendorlib_vfs.toml, never blanket-copied. + #punkcheck records live at src/vfs/.punkcheck so they are not baked into kit payloads. + set vfslibs_config $sourcefolder/runtime/vendorlib_vfs.toml + if {![file exists $vfslibs_config]} { + puts stdout "VFSLIBS: no config at $vfslibs_config - skipping vendorlib -> kit vfs propagation" + } else { + if {[catch {package require tomlish} tomlish_err]} { + puts stderr "$A(BAD)VFSLIBS: cannot process $vfslibs_config - tomlish package unavailable ($tomlish_err)$A(RST)" + exit 3 + } + set fd [open $vfslibs_config r] + fconfigure $fd -encoding utf-8 + set vfslibs_tomldata [read $fd] + close $fd + if {[catch {tomlish::to_dict [tomlish::from_toml $vfslibs_tomldata]} vfslibs_dict]} { + puts stderr "$A(BAD)VFSLIBS: failed to parse $vfslibs_config\n$vfslibs_dict$A(RST)" + exit 3 + } + #tomlish::to_dict values are type-tagged (e.g {type STRING value x}, {type ARRAY value {...}}) + proc ::punkboot::lib::toml_untag {tagged} { + if {[dict get $tagged type] eq "ARRAY"} { + set plain [list] + foreach el [dict get $tagged value] { + lappend plain [::punkboot::lib::toml_untag $el] + } + return $plain + } + return [dict get $tagged value] + } + set vfslibs_installs [dict create] + if {[dict exists $vfslibs_dict install]} { + set vfslibs_installs [dict get $vfslibs_dict install] + } + if {![dict size $vfslibs_installs]} { + puts stdout "VFSLIBS: no \[install.\] entries found in $vfslibs_config" + } + dict for {iname ientry} $vfslibs_installs { + foreach reqkey {source targets} { + if {![dict exists $ientry $reqkey]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname in $vfslibs_config is missing required key '$reqkey'$A(RST)" + exit 3 + } + } + set isource [punkboot::lib::toml_untag [dict get $ientry source]] + set itargets [punkboot::lib::toml_untag [dict get $ientry targets]] + set isupersedes [list] + if {[dict exists $ientry supersedes]} { + set isupersedes [punkboot::lib::toml_untag [dict get $ientry supersedes]] + } + set source_pkg_folder $sourcefolder/$isource + if {![file isdirectory $source_pkg_folder]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$A(RST)" + exit 3 + } + set pkgtail [file tail $source_pkg_folder] + foreach t $itargets { + set target_vfs_dir [lindex [file split $t] 0] + if {![file isdirectory $sourcefolder/vfs/$target_vfs_dir]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname target '$t' - no vfs folder at $sourcefolder/vfs/$target_vfs_dir$A(RST)" + exit 3 + } + set target_lib_folder $sourcefolder/vfs/$t + file mkdir $target_lib_folder + #supersession first - no silent mixed-version provision (G-035 concerns) + foreach sup $isupersedes { + if {$sup eq $pkgtail} { + continue ;#never remove what we are about to install + } + set suppath $target_lib_folder/$sup + if {[file exists $suppath]} { + puts stdout "VFSLIBS install.$iname: removing superseded '$sup' from vfs/$t" + file delete -force $suppath + } + } + puts stdout "VFSLIBS install.$iname: $isource -> vfs/$t/$pkgtail (if source file changed)" + set resultdict [punkcheck::install $source_pkg_folder $target_lib_folder/$pkgtail\ + -installer make.tcl\ + -createdir 1\ + -overwrite installedsourcechanged-targets\ + -punkcheck-folder $sourcefolder/vfs\ + -progresschannel stdout\ + ] + puts stdout [punkcheck::summarize_install_resultdict $resultdict] + } + } + } +} + if {$::punkboot::command in {project packages libs}} { ######################################################## lappend projectlibfolders lib diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.2.0.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.2.1.tm similarity index 99% rename from src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.2.0.tm rename to src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.2.1.tm index a8086722..a167b7f4 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.2.0.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.2.1.tm @@ -8570,7 +8570,7 @@ namespace eval punk { punk::args::define { @id -id ::punk::helptopic::tcl - @cmd -name "help tcl" -summary "Tcl version warnings." -help "Show the running Tcl patchlevel and build-info, with warnings for known Tcl bugs affecting this interpreter (as detected by the punk::lib::check::has_tclbug_* checks)." + @cmd -name "help tcl" -summary "Tcl version warnings." -help "Show the running Tcl patchlevel and build-info, with warnings for known Tcl bugs affecting this interpreter and known bugs in bundled library packages (as detected by the punk::lib::check::has_tclbug_* and has_libbug_* checks)." @values -min 0 -max 0 } proc tcl {context args} { @@ -8580,8 +8580,12 @@ namespace eval punk { catch { append text \n "Tcl build-info: [::tcl::build-info]" } - #generate warningblocks for each triggered Tcl bug in namespace ::punk::lib::check - set bugcheck_procs [info procs ::punk::lib::check::has_tclbug*] + #generate warningblocks for each triggered bug check in namespace ::punk::lib::check + #has_tclbug_* = Tcl core bugs; has_libbug_* = bundled/vendored library bugs (e.g. tcludp - G-036) + set bugcheck_procs [concat\ + [info procs ::punk::lib::check::has_tclbug*]\ + [info procs ::punk::lib::check::has_libbug*]\ + ] foreach bp $bugcheck_procs { set buginfo [$bp] if {[dict get $buginfo bug]} { @@ -8595,12 +8599,15 @@ namespace eval punk { major {set highlight [punk::ansi::a+ red bold]} default {set highlight ""} } + set indent " " append warningblock \n $highlight "warning level: $level $bp triggered." if {[dict exists $buginfo description]} { - set indent " " append warningblock \n "[punk::lib::indent [dict get $buginfo description] $indent]" } - if {[dict exists $buginfo bugref] && [dict get $buginfo bugref] ne ""} { + if {[dict exists $buginfo url] && [dict get $buginfo url] ne ""} { + #full reference url (e.g. non tcl-core trackers such as tcludp) + append warningblock \n "${indent}see [punk::ansi::hyperlink [dict get $buginfo url]]" + } elseif {[dict exists $buginfo bugref] && [dict get $buginfo bugref] ne ""} { set bugref [dict get $buginfo bugref] append warningblock \n "${indent}see [punk::ansi::hyperlink https://core.tcl-lang.org/tcl/tktview/$bugref]" } @@ -9350,7 +9357,7 @@ punkcheck::cli set_alias punkcheck package provide punk [namespace eval punk { #FUNCTL variable version - set version 0.2.0 + set version 0.2.1 }] diff --git a/src/bootsupport/modules/punk/lib-0.2.1.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.3.0.tm similarity index 99% rename from src/bootsupport/modules/punk/lib-0.2.1.tm rename to src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.3.0.tm index 27aa0795..09da6c9a 100644 --- a/src/bootsupport/modules/punk/lib-0.2.1.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.3.0.tm @@ -8,7 +8,7 @@ # (C) 2024 # # @@ Meta Begin -# Application punk::lib 0.2.1 +# Application punk::lib 0.3.0 # Meta platform tcl # Meta license BSD # @@ Meta End @@ -18,7 +18,7 @@ # doctools header # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ #*** !doctools -#[manpage_begin punkshell_module_punk::lib 0 0.2.1] +#[manpage_begin punkshell_module_punk::lib 0 0.3.0] #[copyright "2024"] #[titledesc {punk general utility functions}] [comment {-- Name section and table of contents description --}] #[moddesc {punk library}] [comment {-- Description at end of page heading --}] @@ -250,6 +250,56 @@ tcl::namespace::eval punk::lib::check { return [dict create bug $bug bugref e38dc74e2 description $description level medium] } + #has_libbug_* procs report bugs in bundled/vendored library packages rather than the Tcl core. + #They are surfaced through the same 'help tcl' warning report as the has_tclbug_* checks. + + #pure classifier, separated for testability - facts in, verdict out + proc libbug_udp_threadexit_applies {udpversion platform tclversion} { + if {$udpversion eq ""} { + return 0 ;#no udp package known - nothing vulnerable to load + } + if {$platform ne "windows"} { + return 0 + } + if {![package vsatisfies $tclversion 9]} { + #tcl 8.6 exhibited apparent immunity in the G-036 investigation (unexplained - see goal detail) + #detection deliberately scoped to Tcl 9 per G-036 acceptance + return 0 + } + return [expr {[package vcompare $udpversion 1.0.13] < 0}] + } + proc has_libbug_udp_threadexit {} { + #tcludp <= 1.0.12 on Windows: the per-thread exit handler UDP_ExitProc closes the + #PROCESS-GLOBAL sockListLock/waitForSock event handles. The first udp-loaded thread to + #exit corrupts the lock for every other udp-loaded thread - their event loops block + #forever in WaitForSingleObject on a dead or recycled handle (timers and thread::send + #unserviced). In punkshell the visible casualty is syslog log workers silently freezing + #(the original exit/quit hang this caused is separately mitigated). Root-caused + #2026-07-08 - see goal G-036 detail. Fixed upstream in tcludp 1.0.13 (per-thread + #UdpThreadExitProc deletes only the event source; ExitSockets closes the events at + #process exit). + #Version-based detection only (per G-036 acceptance) - no behavioural probe. + set udpversion [package provide udp] + if {$udpversion eq ""} { + #not loaded - determine what version would be provided, without loading the binary: + #an unsatisfiable require triggers the package unknown scan (registering ifneeded + #scripts) then fails before any dll load. + catch {package require udp 999999} + set available [package versions udp] + if {[llength $available]} { + set udpversion [lindex [lsort -command {package vcompare} $available] end] + } + } + set bug [libbug_udp_threadexit_applies $udpversion $::tcl_platform(platform) [package provide Tcl]] + if {$udpversion eq ""} { + set verinfo "no udp package found" + } else { + set verinfo "udp $udpversion" + } + set description "tcludp < 1.0.13 on Tcl 9 Windows ($verinfo): the udp package's per-thread exit\nhandler closes process-global event handles - after any udp-using thread exits, every\nother udp-using thread's event loop can freeze silently (e.g. syslog log workers stop).\nUpgrade the bundled/available udp package to 1.0.13+ (see goal G-036)." + return [dict create bug $bug bugref "" url https://core.tcl-lang.org/tcludp description $description level medium libversion $udpversion] + } + proc has_tclbug_safeinterp_compile {{show 0}} { #ensemble calls within safe interp not compiled #https://core.tcl-lang.org/tcl/tktview/1095bf7f756f9aed6bde @@ -9069,7 +9119,7 @@ namespace eval ::punk::args::register { package provide punk::lib [tcl::namespace::eval punk::lib { variable pkg punk::lib variable version - set version 0.2.1 + set version 0.3.0 }] return diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl index 8600aae7..eead4196 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl @@ -22,7 +22,7 @@ namespace eval ::punkboot { variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list] variable non_help_flags [list -k] variable help_flags [list -help --help /? -h] - variable known_commands [list project modules libs packages vfs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion] + variable known_commands [list project modules libs packages vfs vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion] } @@ -1348,6 +1348,10 @@ proc ::punkboot::punkboot_gethelp {args} { append h " $scriptname vendorupdate" \n append h " - update the src/vendormodules based on src/vendormodules/include_modules.config" \n \n append h " - update the src/vendorlib based on src/vendorlib/config.toml (todo)" \n \n + append h " $scriptname vfslibs" \n + append h " - propagate declared vendored platform-library packages into kit vfs lib_tcl trees" \n + append h " - declarations (per-package per-kit, with superseded-version removal) live in src/runtime/vendorlib_vfs.toml" \n + append h " - also runs automatically as part of '$scriptname project'" \n \n append h " $scriptname vfscommonupdate" \n append h " - update the src/vfs/_vfscommon.vfs from compiled src/modules and src/lib etc" \n append h " - before calling this (followed by make project) - you can test using '(.exe) dev'" \n @@ -2353,7 +2357,7 @@ if {$::punkboot::command eq "bootsupport"} { -if {$::punkboot::command ni {project modules libs packages vfs bin}} { +if {$::punkboot::command ni {project modules libs packages vfs vfslibs bin}} { puts stderr "Command $::punkboot::command not implemented - aborting." flush stderr after 100 @@ -2475,6 +2479,97 @@ if {$::punkboot::command in {project packages libs}} { } } +if {$::punkboot::command in {project vfslibs}} { + #G-037: propagate declared vendored platform-library packages into kit vfs lib_tcl trees. + #Participation is per-package per-kit (kit vfs package sets intentionally differ - e.g. twapi + #versions vary per kit) - declared in src/runtime/vendorlib_vfs.toml, never blanket-copied. + #punkcheck records live at src/vfs/.punkcheck so they are not baked into kit payloads. + set vfslibs_config $sourcefolder/runtime/vendorlib_vfs.toml + if {![file exists $vfslibs_config]} { + puts stdout "VFSLIBS: no config at $vfslibs_config - skipping vendorlib -> kit vfs propagation" + } else { + if {[catch {package require tomlish} tomlish_err]} { + puts stderr "$A(BAD)VFSLIBS: cannot process $vfslibs_config - tomlish package unavailable ($tomlish_err)$A(RST)" + exit 3 + } + set fd [open $vfslibs_config r] + fconfigure $fd -encoding utf-8 + set vfslibs_tomldata [read $fd] + close $fd + if {[catch {tomlish::to_dict [tomlish::from_toml $vfslibs_tomldata]} vfslibs_dict]} { + puts stderr "$A(BAD)VFSLIBS: failed to parse $vfslibs_config\n$vfslibs_dict$A(RST)" + exit 3 + } + #tomlish::to_dict values are type-tagged (e.g {type STRING value x}, {type ARRAY value {...}}) + proc ::punkboot::lib::toml_untag {tagged} { + if {[dict get $tagged type] eq "ARRAY"} { + set plain [list] + foreach el [dict get $tagged value] { + lappend plain [::punkboot::lib::toml_untag $el] + } + return $plain + } + return [dict get $tagged value] + } + set vfslibs_installs [dict create] + if {[dict exists $vfslibs_dict install]} { + set vfslibs_installs [dict get $vfslibs_dict install] + } + if {![dict size $vfslibs_installs]} { + puts stdout "VFSLIBS: no \[install.\] entries found in $vfslibs_config" + } + dict for {iname ientry} $vfslibs_installs { + foreach reqkey {source targets} { + if {![dict exists $ientry $reqkey]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname in $vfslibs_config is missing required key '$reqkey'$A(RST)" + exit 3 + } + } + set isource [punkboot::lib::toml_untag [dict get $ientry source]] + set itargets [punkboot::lib::toml_untag [dict get $ientry targets]] + set isupersedes [list] + if {[dict exists $ientry supersedes]} { + set isupersedes [punkboot::lib::toml_untag [dict get $ientry supersedes]] + } + set source_pkg_folder $sourcefolder/$isource + if {![file isdirectory $source_pkg_folder]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$A(RST)" + exit 3 + } + set pkgtail [file tail $source_pkg_folder] + foreach t $itargets { + set target_vfs_dir [lindex [file split $t] 0] + if {![file isdirectory $sourcefolder/vfs/$target_vfs_dir]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname target '$t' - no vfs folder at $sourcefolder/vfs/$target_vfs_dir$A(RST)" + exit 3 + } + set target_lib_folder $sourcefolder/vfs/$t + file mkdir $target_lib_folder + #supersession first - no silent mixed-version provision (G-035 concerns) + foreach sup $isupersedes { + if {$sup eq $pkgtail} { + continue ;#never remove what we are about to install + } + set suppath $target_lib_folder/$sup + if {[file exists $suppath]} { + puts stdout "VFSLIBS install.$iname: removing superseded '$sup' from vfs/$t" + file delete -force $suppath + } + } + puts stdout "VFSLIBS install.$iname: $isource -> vfs/$t/$pkgtail (if source file changed)" + set resultdict [punkcheck::install $source_pkg_folder $target_lib_folder/$pkgtail\ + -installer make.tcl\ + -createdir 1\ + -overwrite installedsourcechanged-targets\ + -punkcheck-folder $sourcefolder/vfs\ + -progresschannel stdout\ + ] + puts stdout [punkcheck::summarize_install_resultdict $resultdict] + } + } + } +} + if {$::punkboot::command in {project packages libs}} { ######################################################## lappend projectlibfolders lib diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.2.0.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.2.1.tm similarity index 99% rename from src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.2.0.tm rename to src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.2.1.tm index a8086722..a167b7f4 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.2.0.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.2.1.tm @@ -8570,7 +8570,7 @@ namespace eval punk { punk::args::define { @id -id ::punk::helptopic::tcl - @cmd -name "help tcl" -summary "Tcl version warnings." -help "Show the running Tcl patchlevel and build-info, with warnings for known Tcl bugs affecting this interpreter (as detected by the punk::lib::check::has_tclbug_* checks)." + @cmd -name "help tcl" -summary "Tcl version warnings." -help "Show the running Tcl patchlevel and build-info, with warnings for known Tcl bugs affecting this interpreter and known bugs in bundled library packages (as detected by the punk::lib::check::has_tclbug_* and has_libbug_* checks)." @values -min 0 -max 0 } proc tcl {context args} { @@ -8580,8 +8580,12 @@ namespace eval punk { catch { append text \n "Tcl build-info: [::tcl::build-info]" } - #generate warningblocks for each triggered Tcl bug in namespace ::punk::lib::check - set bugcheck_procs [info procs ::punk::lib::check::has_tclbug*] + #generate warningblocks for each triggered bug check in namespace ::punk::lib::check + #has_tclbug_* = Tcl core bugs; has_libbug_* = bundled/vendored library bugs (e.g. tcludp - G-036) + set bugcheck_procs [concat\ + [info procs ::punk::lib::check::has_tclbug*]\ + [info procs ::punk::lib::check::has_libbug*]\ + ] foreach bp $bugcheck_procs { set buginfo [$bp] if {[dict get $buginfo bug]} { @@ -8595,12 +8599,15 @@ namespace eval punk { major {set highlight [punk::ansi::a+ red bold]} default {set highlight ""} } + set indent " " append warningblock \n $highlight "warning level: $level $bp triggered." if {[dict exists $buginfo description]} { - set indent " " append warningblock \n "[punk::lib::indent [dict get $buginfo description] $indent]" } - if {[dict exists $buginfo bugref] && [dict get $buginfo bugref] ne ""} { + if {[dict exists $buginfo url] && [dict get $buginfo url] ne ""} { + #full reference url (e.g. non tcl-core trackers such as tcludp) + append warningblock \n "${indent}see [punk::ansi::hyperlink [dict get $buginfo url]]" + } elseif {[dict exists $buginfo bugref] && [dict get $buginfo bugref] ne ""} { set bugref [dict get $buginfo bugref] append warningblock \n "${indent}see [punk::ansi::hyperlink https://core.tcl-lang.org/tcl/tktview/$bugref]" } @@ -9350,7 +9357,7 @@ punkcheck::cli set_alias punkcheck package provide punk [namespace eval punk { #FUNCTL variable version - set version 0.2.0 + set version 0.2.1 }] diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.2.1.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.3.0.tm similarity index 99% rename from src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.2.1.tm rename to src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.3.0.tm index 27aa0795..09da6c9a 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.2.1.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.3.0.tm @@ -8,7 +8,7 @@ # (C) 2024 # # @@ Meta Begin -# Application punk::lib 0.2.1 +# Application punk::lib 0.3.0 # Meta platform tcl # Meta license BSD # @@ Meta End @@ -18,7 +18,7 @@ # doctools header # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ #*** !doctools -#[manpage_begin punkshell_module_punk::lib 0 0.2.1] +#[manpage_begin punkshell_module_punk::lib 0 0.3.0] #[copyright "2024"] #[titledesc {punk general utility functions}] [comment {-- Name section and table of contents description --}] #[moddesc {punk library}] [comment {-- Description at end of page heading --}] @@ -250,6 +250,56 @@ tcl::namespace::eval punk::lib::check { return [dict create bug $bug bugref e38dc74e2 description $description level medium] } + #has_libbug_* procs report bugs in bundled/vendored library packages rather than the Tcl core. + #They are surfaced through the same 'help tcl' warning report as the has_tclbug_* checks. + + #pure classifier, separated for testability - facts in, verdict out + proc libbug_udp_threadexit_applies {udpversion platform tclversion} { + if {$udpversion eq ""} { + return 0 ;#no udp package known - nothing vulnerable to load + } + if {$platform ne "windows"} { + return 0 + } + if {![package vsatisfies $tclversion 9]} { + #tcl 8.6 exhibited apparent immunity in the G-036 investigation (unexplained - see goal detail) + #detection deliberately scoped to Tcl 9 per G-036 acceptance + return 0 + } + return [expr {[package vcompare $udpversion 1.0.13] < 0}] + } + proc has_libbug_udp_threadexit {} { + #tcludp <= 1.0.12 on Windows: the per-thread exit handler UDP_ExitProc closes the + #PROCESS-GLOBAL sockListLock/waitForSock event handles. The first udp-loaded thread to + #exit corrupts the lock for every other udp-loaded thread - their event loops block + #forever in WaitForSingleObject on a dead or recycled handle (timers and thread::send + #unserviced). In punkshell the visible casualty is syslog log workers silently freezing + #(the original exit/quit hang this caused is separately mitigated). Root-caused + #2026-07-08 - see goal G-036 detail. Fixed upstream in tcludp 1.0.13 (per-thread + #UdpThreadExitProc deletes only the event source; ExitSockets closes the events at + #process exit). + #Version-based detection only (per G-036 acceptance) - no behavioural probe. + set udpversion [package provide udp] + if {$udpversion eq ""} { + #not loaded - determine what version would be provided, without loading the binary: + #an unsatisfiable require triggers the package unknown scan (registering ifneeded + #scripts) then fails before any dll load. + catch {package require udp 999999} + set available [package versions udp] + if {[llength $available]} { + set udpversion [lindex [lsort -command {package vcompare} $available] end] + } + } + set bug [libbug_udp_threadexit_applies $udpversion $::tcl_platform(platform) [package provide Tcl]] + if {$udpversion eq ""} { + set verinfo "no udp package found" + } else { + set verinfo "udp $udpversion" + } + set description "tcludp < 1.0.13 on Tcl 9 Windows ($verinfo): the udp package's per-thread exit\nhandler closes process-global event handles - after any udp-using thread exits, every\nother udp-using thread's event loop can freeze silently (e.g. syslog log workers stop).\nUpgrade the bundled/available udp package to 1.0.13+ (see goal G-036)." + return [dict create bug $bug bugref "" url https://core.tcl-lang.org/tcludp description $description level medium libversion $udpversion] + } + proc has_tclbug_safeinterp_compile {{show 0}} { #ensemble calls within safe interp not compiled #https://core.tcl-lang.org/tcl/tktview/1095bf7f756f9aed6bde @@ -9069,7 +9119,7 @@ namespace eval ::punk::args::register { package provide punk::lib [tcl::namespace::eval punk::lib { variable pkg punk::lib variable version - set version 0.2.1 + set version 0.3.0 }] return diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl index 8600aae7..eead4196 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl @@ -22,7 +22,7 @@ namespace eval ::punkboot { variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list] variable non_help_flags [list -k] variable help_flags [list -help --help /? -h] - variable known_commands [list project modules libs packages vfs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion] + variable known_commands [list project modules libs packages vfs vfslibs bin info check shell vendorupdate bootsupport vfscommonupdate projectversion] } @@ -1348,6 +1348,10 @@ proc ::punkboot::punkboot_gethelp {args} { append h " $scriptname vendorupdate" \n append h " - update the src/vendormodules based on src/vendormodules/include_modules.config" \n \n append h " - update the src/vendorlib based on src/vendorlib/config.toml (todo)" \n \n + append h " $scriptname vfslibs" \n + append h " - propagate declared vendored platform-library packages into kit vfs lib_tcl trees" \n + append h " - declarations (per-package per-kit, with superseded-version removal) live in src/runtime/vendorlib_vfs.toml" \n + append h " - also runs automatically as part of '$scriptname project'" \n \n append h " $scriptname vfscommonupdate" \n append h " - update the src/vfs/_vfscommon.vfs from compiled src/modules and src/lib etc" \n append h " - before calling this (followed by make project) - you can test using '(.exe) dev'" \n @@ -2353,7 +2357,7 @@ if {$::punkboot::command eq "bootsupport"} { -if {$::punkboot::command ni {project modules libs packages vfs bin}} { +if {$::punkboot::command ni {project modules libs packages vfs vfslibs bin}} { puts stderr "Command $::punkboot::command not implemented - aborting." flush stderr after 100 @@ -2475,6 +2479,97 @@ if {$::punkboot::command in {project packages libs}} { } } +if {$::punkboot::command in {project vfslibs}} { + #G-037: propagate declared vendored platform-library packages into kit vfs lib_tcl trees. + #Participation is per-package per-kit (kit vfs package sets intentionally differ - e.g. twapi + #versions vary per kit) - declared in src/runtime/vendorlib_vfs.toml, never blanket-copied. + #punkcheck records live at src/vfs/.punkcheck so they are not baked into kit payloads. + set vfslibs_config $sourcefolder/runtime/vendorlib_vfs.toml + if {![file exists $vfslibs_config]} { + puts stdout "VFSLIBS: no config at $vfslibs_config - skipping vendorlib -> kit vfs propagation" + } else { + if {[catch {package require tomlish} tomlish_err]} { + puts stderr "$A(BAD)VFSLIBS: cannot process $vfslibs_config - tomlish package unavailable ($tomlish_err)$A(RST)" + exit 3 + } + set fd [open $vfslibs_config r] + fconfigure $fd -encoding utf-8 + set vfslibs_tomldata [read $fd] + close $fd + if {[catch {tomlish::to_dict [tomlish::from_toml $vfslibs_tomldata]} vfslibs_dict]} { + puts stderr "$A(BAD)VFSLIBS: failed to parse $vfslibs_config\n$vfslibs_dict$A(RST)" + exit 3 + } + #tomlish::to_dict values are type-tagged (e.g {type STRING value x}, {type ARRAY value {...}}) + proc ::punkboot::lib::toml_untag {tagged} { + if {[dict get $tagged type] eq "ARRAY"} { + set plain [list] + foreach el [dict get $tagged value] { + lappend plain [::punkboot::lib::toml_untag $el] + } + return $plain + } + return [dict get $tagged value] + } + set vfslibs_installs [dict create] + if {[dict exists $vfslibs_dict install]} { + set vfslibs_installs [dict get $vfslibs_dict install] + } + if {![dict size $vfslibs_installs]} { + puts stdout "VFSLIBS: no \[install.\] entries found in $vfslibs_config" + } + dict for {iname ientry} $vfslibs_installs { + foreach reqkey {source targets} { + if {![dict exists $ientry $reqkey]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname in $vfslibs_config is missing required key '$reqkey'$A(RST)" + exit 3 + } + } + set isource [punkboot::lib::toml_untag [dict get $ientry source]] + set itargets [punkboot::lib::toml_untag [dict get $ientry targets]] + set isupersedes [list] + if {[dict exists $ientry supersedes]} { + set isupersedes [punkboot::lib::toml_untag [dict get $ientry supersedes]] + } + set source_pkg_folder $sourcefolder/$isource + if {![file isdirectory $source_pkg_folder]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname source '$isource' not found at $source_pkg_folder$A(RST)" + exit 3 + } + set pkgtail [file tail $source_pkg_folder] + foreach t $itargets { + set target_vfs_dir [lindex [file split $t] 0] + if {![file isdirectory $sourcefolder/vfs/$target_vfs_dir]} { + puts stderr "$A(BAD)VFSLIBS: entry install.$iname target '$t' - no vfs folder at $sourcefolder/vfs/$target_vfs_dir$A(RST)" + exit 3 + } + set target_lib_folder $sourcefolder/vfs/$t + file mkdir $target_lib_folder + #supersession first - no silent mixed-version provision (G-035 concerns) + foreach sup $isupersedes { + if {$sup eq $pkgtail} { + continue ;#never remove what we are about to install + } + set suppath $target_lib_folder/$sup + if {[file exists $suppath]} { + puts stdout "VFSLIBS install.$iname: removing superseded '$sup' from vfs/$t" + file delete -force $suppath + } + } + puts stdout "VFSLIBS install.$iname: $isource -> vfs/$t/$pkgtail (if source file changed)" + set resultdict [punkcheck::install $source_pkg_folder $target_lib_folder/$pkgtail\ + -installer make.tcl\ + -createdir 1\ + -overwrite installedsourcechanged-targets\ + -punkcheck-folder $sourcefolder/vfs\ + -progresschannel stdout\ + ] + puts stdout [punkcheck::summarize_install_resultdict $resultdict] + } + } + } +} + if {$::punkboot::command in {project packages libs}} { ######################################################## lappend projectlibfolders lib diff --git a/src/runtime/vendorlib_vfs.toml b/src/runtime/vendorlib_vfs.toml new file mode 100644 index 00000000..a079ff3d --- /dev/null +++ b/src/runtime/vendorlib_vfs.toml @@ -0,0 +1,32 @@ +# vendorlib -> kit vfs platform-library propagation (goal G-037) +# +# Declares which vendored platform-library package folders (paths relative to src/) are +# installed into which kit vfs lib trees (paths relative to src/vfs/). Processed by +# 'tclsh src/make.tcl vfslibs' - and automatically as part of 'tclsh src/make.tcl project' - +# with punkcheck-tracked provenance (records in src/vfs/.punkcheck, outside kit payloads). +# +# Participation is per-package per-kit by design: kit vfs package sets intentionally differ +# (e.g. twapi versions vary per kit), so nothing is blanket-copied and a vendorlib drop does +# not force a package into every kit. +# +# Cross-generation installs are allowed when the package supports both Tcl major versions +# (e.g. tcludp 1.0.13 ships tcl9udp1013.dll + udp1013t.dll with a pkgIndex.tcl that selects +# by generation - so the same source folder serves lib_tcl9 and lib_tcl8 targets). +# +# Keys per [install.] entry: +# source - package folder path relative to src/ +# targets - list of kit vfs lib folders relative to src/vfs/ +# supersedes - optional list of legacy/older package folder names to remove from each +# target before install (explicit - no silent mixed-version provision, per +# the G-035 concerns) +# +# This file is a candidate for absorption into the mapvfs toml conversion (goal G-024). + +[install.tcludp] +source = "vendorlib_tcl9/win32-x86_64/tcludp1.0.13" +targets = [ + "punk9win.vfs/lib_tcl9", + "punk9win_for_tkruntime.vfs/lib_tcl9", + "punk8win.vfs/lib_tcl8", +] +supersedes = ["udp1.0.12", "tcludp1.0.12"] diff --git a/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/broadcast.tcl b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/broadcast.tcl new file mode 100644 index 00000000..e4c1bfbc --- /dev/null +++ b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/broadcast.tcl @@ -0,0 +1,39 @@ +#!/usr/bin/env tclsh +# multicast.tcl - Copyright (C) 2004 Pat Thoyts +# +# Demonstrate the use of broadcast UDP sockets. +# +# You can send to ths using netcat: +# echo HELLO | nc -u 192.168.255.255 7772 +# +# $Id: broadcast.tcl,v 1.1 2004/11/22 23:48:47 patthoyts Exp $ + +package require udp 1.0.6 + +proc udpEvent {chan} { + set data [read $chan] + set peer [fconfigure $chan -peer] + puts "$peer [string length $data] '$data'" + if {[string match "QUIT*" $data]} { + close $chan + set ::forever 1 + } + return +} + +# Select a subnet and the port number. +set subnet 192.168.255.255 +set port 7772 + +# Create a listening socket and configure for sending too. +set s [udp_open $port] +fconfigure $s -buffering none -blocking 0 +fconfigure $s -broadcast 1 -remote [list $subnet $port] +fileevent $s readable [list udpEvent $s] + +# Announce our presence and run +puts -nonewline $s "hello, world" +set forever 0 +vwait ::forever + +exit diff --git a/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/bug1158628.tcl b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/bug1158628.tcl new file mode 100644 index 00000000..be3c2c5f --- /dev/null +++ b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/bug1158628.tcl @@ -0,0 +1,56 @@ +#!/usr/bin/env tclsh +# bug1158628.tcl - Copyright (C) 2005 Pat Thoyts +# +# "On windows XP, I have a GUI that has an exit buttons which when +# pressed does: {set done 1; destroy .;exit} If there is an open UDP +# channel with a fileevent on it, the program will not exit -- +# i.e. task manager still shows it. Also if I have the console up, the +# console goes away when the exit button is invoked, but the program +# does not exit. NOTE -- all windows are correctly destroyed (or at +# least withdrawn)" +# +# The fault is calling Tcl_UnregisterChannel in the udpClose function. +# We must let tcl handle this itself. Solved by Reinhard Max. +# +# This script demonstrates the problem. Using udp 1.0.6 the program hangs +# after printing "Exiting...". With the fix applied it properly exits. +# +# $Id: bug1158628.tcl,v 1.2 2005/05/19 20:46:23 patthoyts Exp $ + +#load [file join [file dirname [info script]] .. win Release udp107.dll] +#load [file join [file dirname [info script]] .. i386-unknown-openbsd3.6 libudp107.so] +package require udp + +variable forever 0 + +proc Event {sock} { + variable forever + set pkt [read $sock] + set peer [fconfigure $sock -peer] + puts "Recieved [string length $pkt] from $peer\n$pkt" + #set forever 1 + return +} + +proc Listen {port} { + set s [udp_open $port] + fconfigure $s -blocking 0 -buffering none -translation binary + fileevent $s readable [list Event $s] + return $s +} + +proc Exit {sock} { + puts "Exiting" + exit 0 +} + +if {!$tcl_interactive} { + puts "Bug #1158628 - hangs in exit if open udp channels" + puts " Using a buggy version, this will hang after printing Exiting..." + puts "" + set sock [Listen 10245] + puts "Wait 1 sec..." + after 1000 [list Exit $sock] + vwait forever + close $sock +} diff --git a/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/chat.tcl b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/chat.tcl new file mode 100644 index 00000000..3cd65509 --- /dev/null +++ b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/chat.tcl @@ -0,0 +1,81 @@ +#!/usr/bin/env tclsh +# chat.tcl - Copyright (C) 2004 Pat Thoyts +# +# This is a sample application from TclUDP. +# +# This illustrates the use of multicast UDP messages to implement a +# primitive chat application. +# +# $Id: chat.tcl,v 1.2 2007/04/10 23:36:14 patthoyts Exp $ + +package require Tk 8.4- +package require udp 1.0.6 + +variable Address 224.5.1.21 +variable Port 7771 + +proc Receive {sock} { + set pkt [read $sock] + set peer [fconfigure $sock -peer] + AddMessage $peer $pkt + return +} + +proc Start {addr port} { + set s [udp_open $port] + fconfigure $s -blocking 0 -buffering none -translation binary \ + -mcastadd $addr -remote [list $addr $port] + fileevent $s readable [list ::Receive $s] + return $s +} + +proc CreateGui {socket} { + text .t -yscrollcommand {.s set} + scrollbar .s -command {.t yview} + frame .f -border 0 + entry .f.e -textvariable ::_msg + button .f.ok -text Send -underline 0 \ + -command "SendMessage $socket \$::_msg" + button .f.ex -text Exit -underline 1 -command {destroy .} + pack .f.ex .f.ok -side right + pack .f.e -side left -expand 1 -fill x + grid .t .s -sticky news + grid .f - -sticky ew + grid columnconfigure . 0 -weight 1 + grid rowconfigure . 0 -weight 1 + bind .f.e {.f.ok invoke} + .t tag configure CLNT -foreground red + .t configure -tabs {90} +} + +proc SendMessage {sock msg} { + puts -nonewline $sock $msg +} + +proc AddMessage {client msg} { + set msg [string map [list "\r\n" "" "\r" "" "\n" ""] $msg] + set client [lindex $client 0] + if {[string length $msg] > 0} { + .t insert end "$client\t" CLNT "$msg\n" MSG + .t see end + } +} + +proc Main {} { + variable Address + variable Port + variable sock + set sock [Start $Address $Port] + CreateGui $sock + after idle [list SendMessage $sock \ + "$::tcl_platform(user)@[info hostname] connected"] + tkwait window . + close $sock +} + +if {!$tcl_interactive} { + set r [catch [linsert $argv 0 Main] err] + if {$r} {puts $::errorInfo} else {puts $err} + exit 0 +} + diff --git a/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/multicast.tcl b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/multicast.tcl new file mode 100644 index 00000000..b0de2ebc --- /dev/null +++ b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/multicast.tcl @@ -0,0 +1,45 @@ +#!/usr/bin/env tclsh +# multicast.tcl - Copyright (C) 2004 Pat Thoyts +# +# Demonstrate the use of IPv4 multicast UDP sockets. +# +# You can send to ths using netcat: +# echo HELLO | nc -u 224.5.1.21 7771 +# +# $Id: multicast.tcl,v 1.3 2007/04/10 23:49:38 patthoyts Exp $ + +package require udp 1.0.6 + +proc udpEvent {chan} { + set data [read $chan] + set peer [fconfigure $chan -peer] + set group [lindex [fconfigure $chan -remote] 0] + puts "$peer ($group) [string length $data] '$data' {[fconfigure $chan]}" + if {[string match "QUIT*" $data]} { + close $chan + set ::forever 1 + } + return +} + +# Select a multicast group and the port number. +# +# We have two groups here to show that it's possible. +# +set group1 224.5.1.21 +set group2 224.5.2.21 +set port 7771 + +# Create a listening socket and configure for sending too. +set s [udp_open $port] +fconfigure $s -buffering none -blocking 0 +fconfigure $s -mcastadd $group2 -remote [list $group2 $port] +fconfigure $s -mcastadd $group1 -remote [list $group1 $port] +fileevent $s readable [list udpEvent $s] + +# Announce our presence and run +puts -nonewline $s "hello, world" +set forever 0 +vwait ::forever + +exit diff --git a/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/udpcat.tcl b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/udpcat.tcl new file mode 100644 index 00000000..284aec67 --- /dev/null +++ b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/demos/udpcat.tcl @@ -0,0 +1,65 @@ +#!/usr/bin/env tclsh +# udpsend.tcl - Copyright (C) 2004 Pat Thoyts +# +# Demo application - cat data from stdin via a UDP socket. +# +# $Id: udpcat.tcl,v 1.1 2004/11/22 23:48:47 patthoyts Exp $ + +package require udp 1.0.6 + +proc Event {sock} { + global forever + set pkt [read $sock] + set peer [fconfigure $sock -peer] + puts "Received [string length $pkt] from $peer\n$pkt" + set forever 1 + return +} + +proc Send {host port {msg {}}} { + set s [udp_open] + fconfigure $s -blocking 0 -buffering none -translation binary \ + -remote [list $host $port] + fileevent $s readable [list Event $s] + if {$msg eq {}} { + fcopy stdin $s + } else { + puts -nonewline $s $msg + } + + after 2000 + close $s +} + +proc Listen {port} { + set s [udp_open $port] + fconfigure $s -blocking 0 -buffering none -translation binary + fileevent $s readable [list Event $s] + return $s +} + +# ------------------------------------------------------------------------- +# Runtime +# udpsend listen -port N -blocking 0 +# udpsend send host port message +# ------------------------------------------------------------------------- +set forever 0 + +if {! $tcl_interactive} { + switch -exact -- [set cmd [lindex $argv 0]] { + send { + eval [list Send] [lrange $argv 1 end] + } + listen { + set s [Listen [lindex $argv 1]] + vwait ::forever + close $s + } + default { + puts "usage: udpcat send host port ?message?\ + \n udpcat listen port" + } + } +} + + diff --git a/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/pkgIndex.tcl b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/pkgIndex.tcl new file mode 100644 index 00000000..ba2ae7e1 --- /dev/null +++ b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/pkgIndex.tcl @@ -0,0 +1,33 @@ +# -*- tcl -*- +# Tcl package index file, version 1.1 +# +if {[package vsatisfies [package provide Tcl] 9.0-]} { + package ifneeded udp 1.0.13 [list apply {{dir} { + # Load library + load [file join $dir tcl9udp1013.dll] [string totitle udp] + + # Source init file + set initScript [file join $dir udp.tcl] + if {[file exists $initScript]} { + source -encoding utf-8 $initScript + } + }} $dir] +} else { + if {![package vsatisfies [package provide Tcl] 8.5]} {return} + package ifneeded udp 1.0.13 [list apply {{dir} { + # Load library + if {[string tolower [file extension udp1013t.dll]] in [list .dll .dylib .so]} { + # Load dynamic library + load [file join $dir udp1013t.dll] [string totitle udp] + } else { + # Static library + load {} [string totitle udp] + } + + # Source init file + set initScript [file join $dir udp.tcl] + if {[file exists $initScript]} { + source -encoding utf-8 $initScript + } + }} $dir] +} diff --git a/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/tcl9udp1013.dll b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/tcl9udp1013.dll new file mode 100644 index 0000000000000000000000000000000000000000..097011cce2102d47ba66030e7e15eaefb50caf28 GIT binary patch literal 31744 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 I|Kk$)zf3|Y4FCWD literal 0 HcmV?d00001 diff --git a/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/udp.html b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/udp.html new file mode 100644 index 00000000..0366618f --- /dev/null +++ b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/udp.html @@ -0,0 +1,326 @@ + + + +udp - Tcl UDP extension + + + + + +
+

udp(n) 1.0.13 udp "Tcl UDP extension"

+

Name

+

udp - Create UDP sockets in Tcl

+
+ + +

Description

+

This package provides support for using UDP through Tcl. The package provides +a new channel type and attempts to permit the use of packet oriented UDP +over stream oriented Tcl channels. The package defined three commands but +udp_conf should be considered depreciated in favor of the standard +Tcl command fconfigure.

+
+

COMMANDS

+
+
udp_open ?localport? ?reuse? ?ipv6?
+

udp_open will open a UDP socket. If a localport is specified the UDP +socket will be opened on that port. Otherwise the system will choose a port +and the user can use the udp_conf command to obtain the port number +if required.

+

The following keywords can be used to specify options on the opened socket.

+
+
reuse
+

Using this keyword sets the SO_REUSEADDR socket option which permits multiple +sockets to be bound to the same address/port combination.

+
ipv6
+

By default a IPv4 socket is created. When keyword ipv6 is specified, an IPv6 +socket is opened.

+
+
udp_conf channel host port
+

Deprecated in favor of the standard Tcl fconfigure or +chan configure commands.

+

udp_conf in this configuration is used to specify the remote destination +for packets written to this channel. You must call this command before +writing data to the UDP socket.

+
udp_conf channel ?optionName? ?value? ?optionName value ...?
+

Deprecated in favor of the standard Tcl fconfigure or +chan configure commands.

+

In addition to being used to configure the remote host, the udp_conf +command is used to obtain information about the UDP socket. NOTE all these +options are now available using the standard Tcl fconfigure or +chan configure command.

+
+
-myport
+

Returns the local port number of the socket. Read-only option.

+
-remote ?address port?
+

Specifies or returns the remote hostname and port number. Can also be set using +udp_conf channel host port.

+
-peer
+

Returns the remote hostname and port number for the packet most recently +received by this socket. Read-only option.

+
-family
+

Returns whether socket is configured for IPv4 or IPv6. Read-only option.

+
-broadcast ?boolean?
+

Specifies or returns whether can listen and send on the broadcast address. For some systems +a flag must be set on the socket to use broadcast. This option is not permitted when +using IPv6, instead use multicast.

+
-ttl ?count?
+

The time-to-live is given as the number of router hops the packet may do. For +multicast packets this is important in specifying the distribution of the +packet. The system default for multicast is 1 which restricts the packet +to the local subnet. To permit packets to pass routers, you must increase the +ttl. A value of 31 should keep it within a site, while 255 is global.

+
-mcastadd groupaddr
+
+
-mcastadd "groupaddr netwif"
+
+
-mcastdrop groupaddr
+
+
-mcastdrop "groupaddr netwif"
+
+
-mcastgroups
+

tcludp sockets can support IPv4 and IPv6 multicast operations. To receive +multicast packets the application has to notify the operating system that +it should join a particular multicast group. For IPv4 these are specified as addresses +in the range 224.0.0.0 to 239.255.255.255.

+

When specifying only the groupaddr the system will determine the network interface to use. +Specifying the netwif will join a multicast group on a specific network interface. +This is useful on a multihomed system with multiple network interfaces. +On windows you must specify the network interface index. For other platforms the network +interface (e.g. 'eth0') name can be specified.

+

To view the current set of multicast groups for a channel use -mcastgroups

+
-mcastif
+

Returns which interface is used for outgoing multicast packets. UNIX only.

+
-mcastloop ?boolean?
+

With multicast udp the system can choose to receive packets that it has sent +or it can drop them. This is known as multicast loopback and can be controlled +using this option. By default the value is true and your application will receive +its own transmissions.

+
+
udp_peek channel ?buffersize?
+

Examine a packet without removing it from the buffer. Option buffersize specifies the +maximum buffer size. Value must be between 0 and 16.

+

This function is not available on windows.

+
::udp::build-info
+

Return information on the build environment.

+
::udp::getaddrinfo -hostname name ?optionName value ...?
+

Returns a list with info on the IP address matching the specified parameters. +Valid options are:

+
+
-hostname name
+

Specifies which IP address to use for lookup. Can use IP address or name. Required option.

+
-port number
+

Specifies which port number to use for lookup.

+
-service name
+

Specifies which well-known service (i.e. http, https, etc.) to use for port in lookup.

+
-ipv4
+

Only return IP v4 addresses.

+
-ipv6
+

Only return IP v6 addresses.

+
-server
+

Only return services for which hostname is a server.

+
-tcp
+

Only return TCP services.

+
-udp
+

Only return UDP services.

+
+
::udp::getnameinfo address ?ipv6?
+

::udp::getnameinfo will return the name(s) corresponding to IP +address address. With ipv6, address is an IPv6 address.

+
+
+

EXAMPLES

+
+# Send data to a remote UDP socket
+proc udp_puts {host port} {
+    set s [udp_open]
+    fconfigure $s -remote [list $host $port]
+    puts $s "Hello, World"
+    close $f
+}
+
+
+# A simple UDP server
+package require udp
+proc udpEventHandler {sock} {
+    set pkt [read $sock]
+    set peer [fconfigure $sock -peer]
+    puts "$peer: [string length $pkt] {$pkt}"
+    return
+}
+proc udp_listen {port} {
+    set srv [udp_open $port]
+    fconfigure $srv -buffering none -translation binary
+    fileevent $srv readable [list ::udpEventHandler $srv]
+    puts "Listening on udp port: [fconfigure $srv -myport]"
+    return $srv
+}
+set sock [udp_listen 53530]
+vwait forever
+close $sock
+
+
+# A multicast demo.
+proc udpEvent {chan} {
+    set data [read $chan]
+    set peer [fconfigure $chan -peer]
+    puts "$peer [string length $data] '$data'"
+    if {[string match "QUIT*" $data]} {
+        close $chan
+        set ::forever 1
+    }
+    return
+}
+set group 224.5.1.21
+set port  7771
+set s [udp_open $port]
+fconfigure $s -buffering none -blocking 0
+fconfigure $s -mcastadd $group -remote [list $group $port]
+fileevent $s readable [list udpEvent $s]
+puts -nonewline $s "hello, world"
+set ::forever 0
+vwait ::forever
+exit
+
+
+

HISTORY

+

Some of the code in this extension is copied from Michael Miller's tcludp +package. (http://www.neosoft.com/tcl/ftparchive/sorted/comm/tcludp-1.0/) +Compared with Michael's UDP extension, this extension provides Windows +support and provides the ability of using 'gets/puts' to read/write +the socket. In addition, it provides more configuration ability.

+

Enhancements to support binary data and to setup the package for the Tcl +Extension Architecture by Pat Thoyts.

+

Support for IPv6 and allowing a multicast join on a specific network interface is added by Huub Eikens.

+
+

See Also

+

socket

+
+

Keywords

+

I/O, IP Address, TclUDP, UDP, asynchronous I/O, bind, channel, connection, domain, host, name, network, network address, networking, socket, udp

+
+ +
diff --git a/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/udp1013t.dll b/src/vfs/punk8win.vfs/lib_tcl8/tcludp1.0.13/udp1013t.dll new file mode 100644 index 0000000000000000000000000000000000000000..11a3784f4e8242ea9e4ee4a04a65a61741103c84 GIT binary patch literal 30720 zcmeHw3wTpiw*OAsl$184ltzlsAz+xPk76O9Er=wN;EARXp&;X{Ep19GZ4=Xkha$G7 z6~ZyvQD@{j&REoOUd~uwJar5fX)6ebj^Z$*Rk<@M)tSmTSbUxTZ|!~3ru8xN|9|&> z-~Ik~!gsRvW9_xqUVH7e*V_A}6kpTIk{M$ucoc=Poq+VQ^6zi|(rFkQGkNzI_DtF< zQ+8@xuS_Yau4yp&{N7dm@;XyRd40V%U|Q)h`GfVQntGFc@lsQrx6(6RuODqzNjLo7 z^Zv!pq_@YPjFY3EoQ-_ONu3JEJUNf&4IH+eG(33`@|Pam+CCrf#HvC2C}u}3TmZj6`pv#F-^ zq%n+1D7i?Kx?F&nJl%-li*yoWsk~?ivLh&fP@ND`5-S6$XcPxu&5M%u2FA*WY=E&g zKn4!ifj((DjpScorTnZ{1a zk?=90>*@Z=@<2IbM<=493=c6uvPt+@p+?gM8tYF-MI#>SyBbfz$I4iAx=$eTF=DJ- zpo23Zkb!5NzN)IG5;7vzxPXYBk1|6aE8+PY{1qtTvTH|z#^A@3@Ue0{Ppub-WmF!; zL-f|*N%&Y9yDTC5|5Z|oXy3gB(|u6=xWaaGjw|dko0cwHezg?(pwKDzOOaahI!XS6 zB%g^LP!vT9*P8QO@*$VpA$8i#CX$FGzbVN&Db%e|jD{tQg}RlNs6pB*h4vMe-f;c3 zY7f?>plPL61-w-x_nNhg+2y?`$)icyrDTCv_&T%6wu|zXql)q8i-~>*NLk}_;`|b( zj`*~KImNgPn3kiv*f^pYRVhRz45|Q6o)od0S5VHR7^ec&CF_3;3u+tQh~nC1yMXREba}Ii}omBpJDu4+FCd zJBo7@V>fUNJEGxaOH`8Y6rEcR#;%x6q#iU|iJ7K-#3kmh106R~$Cw8B4GcNXQgCY1 zT-rD`?gGXQyg%dGs~B?-RbW}Mpmu@=>wAFaY;e9zGz8KEGBC_355(U6Qc>h%Ezw}6 zVF#)dV-#|%3Xh^5uQL}wlBQDVzz2D!U(^LhfqZCR9;%!s z7>jKueRGau(`>v~Kl<23qE_5_5^Mxkd-EN(8ePSV$+4E`dPv7O9;7Atkg}wpcQd9EL)21dIY{%)26$&=sx3#QZ~^*5 zO}HA}+-Z)2&)Dtg7gdx+ji`^+p%Ck}fcl^1QtNp4&{Yg{7EuwC!sh`4YB>&S5%-)p zCB?Y!O5&$qf>AI`wj-@X?S&gsHNn$PvJ)y**ZI&+!VvYBXo8=(lA}`OKF&{^O~rVc zrX#WlCbJT$s&FU^W8!G&7i%+)8QzpZykRAr|DqIW%|ga7xe++buwAn`WwaD=YMP_L zQ}L!)dD8hWOOa+S4k>a~hGB9up#xvHIU`AmST)Vv!BejA3$(snMSJqlaWG8^^F$vM zHdH{Ygm-c)72ZR+mJc`1!d$Fhjkz#^7R?20lGhbBLD+J-6qYi{2!&VV0Bs4-V3@G} zB>Bfe`;Qx19{d7}PuAz(gi7NRPO8KtQCBcMhYQHEJ!mGqcFNb9obu{Cr`%BBlyAW{ z^uDdsc7yG@j$PmwjhA314x*ov@DfN_+y!by5ml(^d`c&NXn~#=Dq?|YX!-;-jTpCn zt|*;ElpC#xe&93ksI-;ATylE@ej{X((KJ;tCUK_qtA|SZVZz9~NXoR|qcX8OVee?^ zX3~N!<7QgB2@a6DVBILOoUW>h&ky<<3(8opXk*1wK$_|9pb?i*KeCbRzx<`ta@5fL zB6!9=Ts73*6?uS+kFCgJxcffTN<~lc_1wIPGLZ+|sFe42Cev{3G8Uo(GkCU3UZMjF z&}X^JRaA0N3XD>swz*M%KfZ!STWG-AV4ywlG94DoK}q3e@iLv#c}2K6!;NiPl5eMe zcouL`_`wV}rv3_oI?QHhug=+YhL-HD(2;wDJ{&c)xX2jG`do}jF+OxTluQX4Q7{Pw z+_DFIRh=F3KYF9wI~~l z#Q30Yf8_j9YuH2HHjp97&3`0*71oClKJ!NslcaP=bKlwUib`OHSnt%HDxnN|s8DS+ z8nrS$uONY;qFK0r8omx@9JFy5I~QUMQ^DTBgH}?5en=l}8>;|>-R?n2XxKIpdvJAi_0(&*UBz-<)9tNCadA zUcLaZX&=c5j+1pMb~<>Wv<1S`f-_X3!-fUXo~#8Pw!;FOthmCJ=tt-gklI1dxg}xY?UjZ`E3JDEM zyV`{>lhQhXV)BN{obo~J(k=)cuHzAfa5I1lD+*!QvE;DsIIrO;jo)bS3vMvlMW`5T^3c7hse-?WE4uXQ?( zk3~)~-oJob&X%815AyY$QluDe;sWTxQd3MEy;_p z!n+;ves1QS@|%kBF%ZK1>J?)NI+1k{SlB>@hh*iFRT;^}lgWUD^U!P4zBW{m$3Vf$ z(3VX4hWN|2oU|>pD~H75#Ei%>mwXELB{+2`fi4Lq)eiqxB20Vc5ewuNZ%8B>^eH^v+c3 zruTk)6?t_Vlfzz}tKxDUR-+`3kKKyHCkffPFWr{1aPuY5f9=QR#FYVFkL-otdv%5s zk;yHz?bM{u)IJEd3_Db}@o?O;ls5@?E=2AgsQeaGKmv~4DDXtWKuCZcIrN3HF-3l< z4+4MRMgk9yfiP%&TV43Vd{T59m&cHF6XLr97+%cs3h;!UJ|&~J!YRKB5o0sB2>pt% zna&VlV5=xfARD-1H?0gbG+`Ksv8^V>xYNzq6+4r`iA&ex&nD=Q@~TIohY)eJ%!|7!SI0D)PK7ePiFi{;OT{7r<~X!R&s4ecL%+t}|qe}AiCd*)?^?HTO+SA(Mv z>mY#ru;plAGF&6tOh9bI^IS71WQ8?VZ75F;i`(NmVqLZr1``1+ohiKvgV_Zsq5r`3 z0Nh}XLhdZi%_*1sQK#d6=%a#--6?n3zkx>Tc!Ez87RrFa;8ak= zNTJ&y5SATJga(ofEf0xKUf>Oif6g5c99LnhYvgLA{h6rQf#t(dhgEJC{oF6f%XaWa zRv?82G=bF6t@mqgt(IC&2hIl9Wx#`r^L?A@;($DPIP&<&6);z{duC!UoI_a9+Iwi1 z<=yojP3H6rEk750ju+gFuy$QJi_ zeoLryN;SmNrC`a@y;TYqcaonOE`A~Gpu?{ap@xgG*A~Bt=N&wMAsM>Dw?5SA=tL{l z={UgmRqlcjv*ew0K|^0qj2Cc51JmPTo5h(l{$~Dgv4P@4it&ZH*bA=WQY*%c+DwK_ ztIjai@i)czD5_+~{h)~-0WaiRg6~w6|&jfi@;;VxVK-MFm9)O z7kQ}g7Gu9f$AN`oLYjXiS?w4)^g|SLn{!aPm#mFb{t@@$>C8K--0MRTy6|#l4ylz& ztJsHJvRIy&%TeTyoS6*drhR1S==`GehHDcX64idk&-cg)h9T7&Ib}E`y~6TYj;mIW zwo!-<#tv4GM+hgsPveYRy%>5ySWh@g7X}Gh-9RvGdL9+DTbYFM8?=1CMy-S`)ubAO zNO7h(FC+TKWpf~h4dxX|n}1@!n1c&JZ#`NNr97N~gZ?m6dA&+4Pf&A3S~9t)!0?e6 zJE(!+xcl23Y5&aGf0@>i!QgK_0?i;TsFz|Kp?!F&f%jWMF;82iQq23mr|s8AuHp zLVHLZncc7m@poUQn&s=0$`oV!#ne^qxgh?py9V!LRmW2Q^3VL5_$c{)D@7hR|AJ0O znm!QASF2r)u4%h%u?kgw^lIYeEgS{o?g!fQGF=4u=Hj5geLHh8lry}6id za90adEKzRF$WV}?Npy&8CvgAjmR<##x1^l5_7yPq7D14BtX~Kj%Au)-Tv-+?@kD zVk+$rRT+j{T7>;81T=Mtj3;&>todld(SzrR2AG+Pfr~1$N=?i`FS$8`D3gHFcN|Dc ziLgA?l6)_jV{&gxxaikG^f(+T!SrzMK9RqSpZB@UK+oNWrtojaAaLMkbw|KbqjE1z zxPrKMjczVlZ+I0G`xZzgE^R9bFut@QHXaWSifx#$fA=gdLBwME?293SUHn8+D^cI* zE>Tj3?YR7bsAfUZy(`2TG<_A%utUx!Q5YwIwB4|yt9Kf3t%ZB|6+m;oX!w_{18j4c*H8{KR~H^d=lBKFL?)Xmb&z6TOz=^iFaZYIYRaWkn}NrT7@8p%ZcoPoZe zpCsM=7eQ**@~Z^xNnCi2lrHqp4VLB~pmPfR?n z_pR8oV84a8fCCLDE_~HE$=JoK57Px8e4Tr^1&0Ec#mx`ScpSM(I3y|;ddU! z2+M@c%t_b1+GasR*eDzywnW9*Vc0A~?TIxKY*rb!S+`TW zh;hwf*sMEmmu4WB{Ic?{3x_}CSR9Bwjv%;~c3a!74Bp29RG>Qc^hb))7ZeAbKvYYA zLZzic&2#&aUp$nLoJM~BQ2q-X)%IP8@jZ&sW9Fa7GIPl<(Y>?@-4Smuqlm`{R!knZ z%yfVNn^Ft?2<@}d2~$Vwj|v#mg8g3tzM!Tl|NDgWemS#1JI{& zy~nvA?$2?pB@U&vPoq-3(hhTSyuJhe0)^+x;%Azz@EKHZ_tj#M;~{#rCF-m=hQ;2G z7y-UQvD^#wvq#n?L6X;S!lYz)K_WDeY}iy!TV?neb33;=f8WE`&)l~Skt>mM$|ZjS zm+Cfz%US&dZd0^bpaZphZIRGNY7|#kzF)#M6#J#2`C-(d=N_Q)nc^BQYmXpLTZEnW zL1!Se9N}tRfVFL>&}c6j&Z7e-!`=5I3G1a8uYmAqXt>AeT`nTmO(5iyU4MeWX!juJ z*9r}>q6s=Yf<9yBw-tKdrG_0kWE zYkL6}g6@ub-{U!&U&_6X9F%Yx)CME`5>p^ll0ei<1wHaPGyoWap zJKXYXQrNKq=wU~hVtfq-1?k9A*uXB_V1qxH90(&9Cn=B7)JBYpK}L{QFK7BLn5%P#nogKd$Q1L z(>_u!ezki&f+NI6j+PSv>!6HzzlmppHog~g%3eN#^@u%lPq=6V`scCadV}Pl;r1V! zfOu~q<~Py6PT<%&U|v#X3n}A+xrR*bI~c`IlDh+1GN}sI&TSZr;HfXO3QG@-Gf`*9 z-BJl%zhU#j$lr(I#x6vC`b3{K)Tai0RykTu2Pc!nT#%Tn5sjtDGDri>>|hmI6~u{v zwh--hRb!g?%M8bG>|$s>j(N5mwe6&0Oi(O}pbp2;tzZy z`{+te-)-B4=!phzLIK{-f!!w};bcD1+Luwvy*{Ni6Nu=4CraoNurrhcL=vz){0MDL z@S2g6!XXjs9_I1o7V1VG2iZMMt_UDM`czumC=!shH=#ZXZxDzFt;=ES=uWngMlNeVkO0WY zHJja{WD5v!8+WF-BF5jvBJ*O4IKBuCq!~83;5#_w#ke(o(A>sbv~d?WumEs1pzVg1lM?6g?Yx+_R_(J+>V7cf2>MXA zjml`zh)9FtGQJFH{#pb+E+ZBrKEtsJ*ldZSq81gw?+q@G)))mLvUb@JEefG9E$@?T z1a&GAQ*ekE>c0mLF^UvJ%fnwYisBRyOaVc#u8t!yU_zW2Tm_8xAPP)Gj(UzE8(*cV zl#ih-Q@KajZS9k=$>c(r=zf>D*-c?=EcH#kq8>sEGCUaZc&xgu=Me!2DF^GOcdFp^ z7A#~5hhOWEeIE0|Ac>E(E}&4Rgtz-7oZyj2=rWGf)FlfYm2em)6g<8>?i34)?0*JGHSMF7 z2m62h4R{H!4+rep9MO{vk}j4z)ul^^={i+!`gXNGftYX-q(boP(usA6{uaEgJaqBw zLK?-(v_2!oW9Ud&AFh>fgz^SYiB$ZSe-+=lOm&~=euFNA8AgW=@3RAN=V^SE!KJcBx5diD zdk9L0OYU*b{ZnrlR8HC7>7cU$!lN>JBd7_k5=+CZ5H6}|kEXf;)QKKI`y=pdh&(}6 zvg>Uf?_HH0)rf48iOPUcVIr~P_Ov}cyUGMJ^Vca`NyYma+?X6 z-XK!ZbJueE`hBpE2skOOR1eAN#o&Yk@>4Tu92lFxKZh}MVx!_UI4~x*l}f&V*DH__*Vmcvw=N6 z0UPcU$9_XGt{cQY6vrO5-_eO#g-CKAzKp2rlf|IEfuGzB9ILqVn_15Nz994>7TC97 zS%8HLJvhqkm;)YUCyFk2oS@SDEmUy-gLvXXiYKPf*LVaKfi#T4f<}GcL1H;2j86HL zKKKQCJLO397bAfYw?m-c!r>244dkI_~;8Fm6(#h}1)6el{d-;CqPpUx+M zv562HTY&~%sD=s`>F5$z_5Y;MXUTy|Q13TFWHGfR~0V;TA>5C#w_;nyn0o4!Lb$o&I^38Og~LDb!^ zkO7Zk49|irjV3;ZzIjk-E@Yvsi9sv85HkjG59na-=W>R=Ztf-dPBTpxnRz21y}3*i ziZ_=t@z$~lZ!DK~@ZY=mgvEapq;N(TqY#LE`}DZG;ky<$%m-pUzo#>VU~j~oLUBPO z4I1|!-~(%6oSmGMt!bF6LKd9UNjnP4jmL3x2CNg%Ghod_4xQoXocfTpuZZH**C@I~ z0Ep%&-62X%YUzN$-Ns9eNkUY7t2=;Dz`&a{qY{3?pFX6^4|ID1TLTGHT;&2UGnwOE zC-8oDiuh2gBgXxFBN@WFNMLz!8kd08J_0LKV67jHwGbKvX$|&x5~~cHg@$7VM$p

Dmou71lk^frk)S0Qdll}n+WpM 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