Browse Source
'make.tcl bootsupport' failed three times this session replacing the zip-based
punk::mix::templates modpod, always with:
error copying "<src>" to "<dst>": invalid argument
Root cause, demonstrated rather than inferred. A zipfs mount MEMORY-MAPS its archive
(tclZipfs.c CreateFileMappingW + MapViewOfFile), and windows refuses to overwrite a file
with a user-mapped section open - ERROR_USER_MAPPED_FILE (1224). Any holder blocks it: a
running punk shell that loaded the modpod, a src-mode session, or the build itself.
Reproduced directly: the same file copies fine unmounted, fails while zipfs-mounted, and
copies fine again once unmounted.
The message is a dead end BY CONSTRUCTION, which is why two earlier attempts at
characterising this (in commit messages and in the archived G-124 detail file - first "a
modpod make.tcl has itself mounted", then "a transient lock") were both wrong in different
ways. Tcl_WinConvertError maps only Win32 codes 0..267 and sends everything above them to
errorTable[1], which is EINVAL - so EVERY high-numbered windows error surfaces as "invalid
argument", naming neither cause nor remedy. Byte-identical code in 8.6 and 9.1b1.
FIX. ::punkboot::replace_possibly_mapped_file tries the ordinary copy and, on failure,
falls back to DELETE-THEN-PLACE, which windows does permit: unlinking a mapped file leaves
the holder reading its own mapping while new content lands at the name (measured: delete
succeeds while mounted, the mount keeps serving its old content afterwards). Replacement
content is staged to a sibling <target>.punkboot-new first, so a mid-sequence failure can
never leave the target missing; a 'broken' return - unlinked and unrestorable - is reported
as a build failure naming the file to restore by hand. This replaces a narrower prior
workaround that only recognised the already-identical case and otherwise recorded FAILED.
DIAGNOSIS. ::punkboot::mapped_file_hint fires when a genuine failure carries the catch-all
message and explains what it actually means and which process class to look for, instead of
leaving "invalid argument" bare. It stays silent for unrelated errors.
Verified on the real failure: the bootsupport run that has failed all session now reports
"(in-place overwrite refused - replaced by delete-then-place; target was memory-mapped, e.g
a mounted modpod)" and completes, with no hand-copy and no rerun. Helper-level checks cover
the unmapped path (plain copy, unchanged), the mapped path (recovers, content correct,
holder's mapping intact, no temp left behind) and both hint branches. Full suite 1167 tests,
1146 passed, 1 failure - core/tcl exec-14.3, the documented baseline.
Documented in src/bootsupport/AGENTS.md, including the general warning that 'invalid
argument' from ANY windows file operation in Tcl means "some windows error above 267" and
must never be read at face value.
Also drafts G-132 (user-approved): the errno catch-all is upstream's defect, not ours, and
deserves reporting. The goal is deliberately shaped so an AGENT DRAFTS AND THE DEVELOPER
SUBMITS - no account use, no web form, no API - following the G-039 precedent, with the
submission-ready text staged in TEMP_REFERENCE and the finding, reproducer, eventual ticket
URL and disposition kept in the tracked detail file, because TEMP_REFERENCE is one
'git clean -xdf' from gone. The report's claim is deliberately narrow: the errno conversion
is plainly wrong, while the related fact that 'file copy -force' over a mapped file succeeds
on unix and fails on windows rides along as context rather than as a second demand.
punkshell's workaround is explicitly not removed if upstream fixes it - an upstream fix
dates it rather than deletes it.
Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com
master
6 changed files with 290 additions and 23 deletions
@ -0,0 +1,146 @@ |
|||||||
|
# G-132 Draft the Tcl-on-windows errno catch-all report for manual submission |
||||||
|
|
||||||
|
Status: proposed |
||||||
|
Scope: TEMP_REFERENCE/tcl-windows-errno-catchall-TICKET-DRAFT.md (submission-ready ticket text - STAGING ONLY, unversioned, and the agent does not submit it); this detail file (the tracked record: finding, verbatim reproducer, the submitted URL once the user files it, and the upstream disposition); src/bootsupport/AGENTS.md and src/make.tcl (the existing workaround and its warning - dated, not removed, if upstream fixes it) |
||||||
|
Goal: the Tcl-on-windows errno catch-all - every Win32 error above 267 surfacing as "invalid argument" - is written up as a submission-ready upstream report FOR THE DEVELOPER TO FILE MANUALLY, with a minimal reproducer and suggested fixes, and punkshell keeps the tracked record of the finding and whatever upstream decides, so the diagnosis is never re-derived the next time a high-numbered windows error appears as a nonsense message. |
||||||
|
Acceptance: the existing core.tcl-lang.org/tcl tracker is searched first and the result recorded here - an existing ticket is a valid outcome, and this goal then only links it and records its state; if unreported, a complete submission-ready ticket text is written to TEMP_REFERENCE/tcl-windows-errno-catchall-TICKET-DRAFT.md carrying title, summary, the minimal reproducer, the source citations (win/tclWinError.c - table extent 0..267 and the errorTable[1] fallback, verified present in both 8.6 and 9.x), the concrete windows code (ERROR_USER_MAPPED_FILE 1224), the cross-platform observation that the same 'file copy -force' succeeds on unix, and the candidate fixes with their trade-offs; THE AGENT DOES NOT SUBMIT IT - no account use, no web form, no API, no mail - submission is the developer's action alone, and the goal is not complete until the developer has submitted or declined; because TEMP_REFERENCE is ignored by both VCS and is one 'git clean -xdf' from gone, the reproducer and the source citations are ALSO carried verbatim in this tracked detail file, so the draft file is a convenience for pasting and never the only copy; once the developer submits, the ticket URL and the upstream response are recorded here with a disposition (fixed-in-release / declined / duplicate / superseded), and if fixed, the fixing release is named so the make.tcl workaround and the src/bootsupport/AGENTS.md warning can be dated; and the punkshell-side workaround is NOT removed by this goal - it must keep working on every runtime punkshell supports regardless of what upstream does. |
||||||
|
|
||||||
|
## Context |
||||||
|
|
||||||
|
Found 2026-07-27 while diagnosing a `make.tcl bootsupport` failure that had recurred three |
||||||
|
times: replacing the zip-based `punk::mix::templates` modpod in `src/bootsupport` failed |
||||||
|
with |
||||||
|
|
||||||
|
error copying "<src>" to "<dst>": invalid argument |
||||||
|
|
||||||
|
The message is a dead end - it reads as a bad argument, and it names neither the real cause |
||||||
|
nor a remedy. Two earlier attempts at characterising it (recorded in commit messages and in |
||||||
|
goals/archive/G-124-punkzip-reader.md) were both wrong in different ways, precisely because |
||||||
|
the message carries no information. |
||||||
|
|
||||||
|
The punkshell-side cause is understood and worked around (see the Finding below and |
||||||
|
src/bootsupport/AGENTS.md): a zipfs mount memory-maps its archive, and windows refuses to |
||||||
|
overwrite a file with a user-mapped section open. That part is ours and is fixed. |
||||||
|
|
||||||
|
What remains is upstream's: Tcl converts EVERY windows error above 267 to `EINVAL`, so an |
||||||
|
entire class of real, common file-operation failures arrives as the same uninformative |
||||||
|
message. That is worth reporting, and this goal exists so that the report is drafted |
||||||
|
properly and the outcome is recorded - not so that an agent files it. |
||||||
|
|
||||||
|
## Finding (tracked copy - TEMP_REFERENCE is unversioned) |
||||||
|
|
||||||
|
Verified 2026-07-27 against `TEMP_REFERENCE/tcl9` (9.1b1) and |
||||||
|
`TEMP_REFERENCE/androwish/jni/tcl` (8.6.10). BOTH generations carry byte-identical logic. |
||||||
|
|
||||||
|
`win/tclWinError.c` holds `errorTable[]`, whose last entry is index 267 |
||||||
|
(`ENOTDIR /* ERROR_DIRECTORY 267 */`). The converter is: |
||||||
|
|
||||||
|
void |
||||||
|
Tcl_WinConvertError(unsigned errCode) |
||||||
|
{ |
||||||
|
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]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
`errorTable[1]` is `EINVAL` (`/* ERROR_INVALID_FUNCTION 1 */`). So any windows error code |
||||||
|
above 267 that is not in the WSA range collapses to `EINVAL` -> "invalid argument". The |
||||||
|
affected set is not exotic: |
||||||
|
|
||||||
|
303 ERROR_DELETE_PENDING file is in the process of being deleted |
||||||
|
1224 ERROR_USER_MAPPED_FILE cannot be performed on a file with a user-mapped |
||||||
|
section open |
||||||
|
1920 ERROR_CANT_ACCESS_FILE the file cannot be accessed by the system |
||||||
|
1921 ERROR_CANT_RESOLVE_FILENAME the name of the file cannot be resolved |
||||||
|
4390 ERROR_NOT_A_REPARSE_POINT not a reparse point |
||||||
|
|
||||||
|
The specific case that surfaced it: `tclZipfs.c` maps a mounted archive with |
||||||
|
`CreateFileMappingW` + `MapViewOfFile` (9.1b1: :1867 and :1875; `UnmapViewOfFile` at :1475), |
||||||
|
so any process holding a zipfs mount holds a user-mapped section on that file, and windows |
||||||
|
raises 1224 for an in-place overwrite. |
||||||
|
|
||||||
|
Cross-platform observation worth including in the report: the same `file copy -force` over a |
||||||
|
memory-mapped file SUCCEEDS on unix. So the failure is windows-only and silent about it, |
||||||
|
which makes the misleading errno costlier than it first appears. |
||||||
|
|
||||||
|
Measured behaviour, same machine, Tcl 9.0.3: |
||||||
|
|
||||||
|
file copy -force over the archive, not mounted -> ok |
||||||
|
file copy -force over the archive, zipfs-mounted -> invalid argument |
||||||
|
file copy -force over the archive, unmounted -> ok |
||||||
|
file delete of the archive, while mounted -> ok <-- unlink is permitted |
||||||
|
delete-then-place while mounted -> ok, and the holder keeps |
||||||
|
reading its own mapping |
||||||
|
|
||||||
|
## Reproducer (tracked copy) |
||||||
|
|
||||||
|
Self-contained, no punkshell, windows only. Any zip will do - a kit or a zip-based `.tm` |
||||||
|
modpod is convenient. |
||||||
|
|
||||||
|
set f mycopy.zip |
||||||
|
file copy -force somearchive.zip $f |
||||||
|
tcl::zipfs::mount $f probe |
||||||
|
catch {file copy -force someotherfile $f} e |
||||||
|
puts $e ;# error copying ...: invalid argument |
||||||
|
;# actual cause: ERROR_USER_MAPPED_FILE (1224), unrepresented in errorTable |
||||||
|
tcl::zipfs::unmount probe |
||||||
|
|
||||||
|
Expected after a fix: an errno that distinguishes this from a caller error - the message |
||||||
|
should not claim the arguments were invalid. |
||||||
|
|
||||||
|
## Approach |
||||||
|
|
||||||
|
- Search the tracker BEFORE drafting. This is old code present in 8.6 and 9.x, so a prior |
||||||
|
report is entirely plausible and finding one is a complete result for this goal. |
||||||
|
- Draft to `TEMP_REFERENCE/tcl-windows-errno-catchall-TICKET-DRAFT.md` in submission-ready |
||||||
|
form - a developer should be able to paste it without editing. |
||||||
|
- Keep the claim narrow and defensible: the REPORT is about the errno conversion, which is |
||||||
|
plainly a defect. The `file copy -force` portability difference is context in the same |
||||||
|
ticket, not a second demand - upstream may reasonably decline to change copy semantics |
||||||
|
(atomicity, ACL inheritance), and bundling it as a demand weakens the part that is clearly |
||||||
|
wrong. |
||||||
|
- Offer candidate fixes with trade-offs rather than insisting on one: extend the table; |
||||||
|
give the out-of-range branch a better default than `EINVAL` for the sharing/locking family |
||||||
|
(`EACCES` is defensible, `EBUSY` arguably closer); or expose the raw windows code so |
||||||
|
callers can diagnose. Deciding among these is upstream's call. |
||||||
|
|
||||||
|
## Alternatives considered |
||||||
|
|
||||||
|
- File it directly from an agent - rejected, and this is the point of the goal's shape: the |
||||||
|
developer submits. Agent-filed reports on a project's public tracker are the developer's |
||||||
|
reputation and correspondence to own, and a ticket needs someone who can answer follow-up |
||||||
|
questions. |
||||||
|
- Fix it locally and say nothing - rejected: the workaround is in punkshell's build tooling |
||||||
|
only, and every other Tcl user on windows keeps meeting "invalid argument" for a class of |
||||||
|
errors that has nothing to do with arguments. |
||||||
|
- Submit a patch rather than a report - deferred, not rejected. It is a table and the change |
||||||
|
is small, but choosing the right errno per class is a design conversation, and a report |
||||||
|
that names the candidates invites that conversation without presuming its outcome. If the |
||||||
|
developer prefers the patch aim, this goal's Acceptance changes rather than the finding. |
||||||
|
|
||||||
|
## Notes |
||||||
|
|
||||||
|
- PRECEDENT: G-039 (achieved 2026-07-12) filed a Tcl 9 windows console defect the same way - |
||||||
|
agent drafted the full text with repro scripts to `TEMP_REFERENCE/`, the developer |
||||||
|
submitted, and the goal recorded the URL |
||||||
|
(https://core.tcl-lang.org/tcl/tktview/f10d91c2d3). That draft elided the scripts from the |
||||||
|
web submission; whether to do the same here is the developer's call at submission time, |
||||||
|
not something this goal presumes. |
||||||
|
- The punkshell workaround this goal does NOT touch: `::punkboot::replace_possibly_mapped_file` |
||||||
|
falls back from overwrite to delete-then-place, and `::punkboot::mapped_file_hint` explains |
||||||
|
the catch-all when a copy genuinely fails. Both live in `src/make.tcl` and are documented |
||||||
|
in `src/bootsupport/AGENTS.md`. They must keep working on every runtime punkshell supports |
||||||
|
regardless of upstream, so an upstream fix dates them rather than deletes them. |
||||||
|
- Related: G-110 - it studies caching of EXTRACTED shared libs, where extraction today is |
||||||
|
done by zipfs/vfs::zip. Anything that holds a zipfs mount holds a mapping, so the same |
||||||
|
windows constraint applies to any scheme that wants to replace a mounted archive in place. |
||||||
|
- The `invalid argument` reading applies to ANY windows file operation in Tcl, not just |
||||||
|
copies. Treat it as "some windows error above 267" wherever it appears; that guidance is |
||||||
|
recorded in `src/bootsupport/AGENTS.md` because that is where it bit, but it is general. |
||||||
@ -1,4 +1,4 @@ |
|||||||
[project] |
[project] |
||||||
name = "punkshell" |
name = "punkshell" |
||||||
version = "0.27.3" |
version = "0.27.4" |
||||||
license = "BSD-2-Clause" |
license = "BSD-2-Clause" |
||||||
|
|||||||
Loading…
Reference in new issue