0.8.0 --- truncate(2), and the backends that fixed the EAGAIN this port reported - #20
Merged
Merged
Conversation
Only `ftruncate' was answered. The two are not interchangeable to a caller
holding a name and no open file, which is every caller of
`std::filesystem::resize_file': libc++ expresses it as
`detail::truncate(p.c_str(), size)'. Reported in openkal-linux#13, where
operation 76 appears in the trace of an ordinary run.
Composed exactly as `SYS_utimensat' beside it is: resolve the name, open the
file for reading and writing, act, release. Opened for both because openkal
decides at the point of opening what may afterwards be done with a file, and the
divergence that produces from POSIX -- which asks for write permission and not
for read -- is the one already recorded there.
--- and two observations about a directory's modification time ---------------
`examples/posix' gains four observations. Two are the operation above, taken
with the descriptor already closed so that nothing can reach the one that
already worked:
ok: the length is set by name
ok: the length set by name is the length reported
and with the case removed again they are
FAIL: the length is set by name (errno=38)
FAIL: the length set by name is the length reported (errno=38)
The other two record an asymmetry that a consumer read the wrong way round, and
they are here because the C++ library above gives the two overloads one name.
`std::filesystem::last_write_time' throws `filesystem error: in last_write_time'
whether it was reading or writing, so a caller cannot tell from the message
which failed. Measured:
ok: a directory reports its modification time
ok: setting a directory's modification time is refused, as EISDIR
Reading resolves a directory perfectly well. Setting does not, because
`kal_fs_set_modified' takes a `kal_file' and openkal has no form of it that takes
a directory or a name, so this port opens the name as a file. A caller holding a
lock directory reads its timestamp to decide staleness and writes it to refresh
the lock, and only the second fails; openkal-linux#13 reported it as the first.
README.md carries the row. The refusal is asserted rather than merely not
exercised, so that the row is contradicted if openkal ever gains the operation.
Measured: 47 observations, 0 failures, and `OPENKAL_MUSL_TRACE=enosys' over the
whole probe now prints nothing at all.
…nux#13 `openkal-linux 0.7.1` and `openkal-macos 0.6.1` correct `kal_timeout_read` and `kal_timeout_write`, which decoded a borrowed `kal_stream` handle with the decoder for an owned one and so waited upon the descriptor below the one they transferred upon. THIS PORT IS WHERE THAT DEFECT WAS OBSERVED AND IS NOT WHERE IT WAS. libc++ opens a copy's source with `O_NONBLOCK`, `do_read` routes that through `okm_timed_read`, and `SYS_copy_file_range` is a `do_read`/`do_write` loop, so every byte of a file copy passed through a wait upon the wrong descriptor. `EAGAIN` is not in libc++'s fallback list, so `std::filesystem::copy_file` reported `Resource temporarily unavailable` for two ordinary files. Nothing in this port needed to change: the route through `O_NONBLOCK` was correct and what it reached was not. `.agents/docs/2026-08-30-issue13-round-four-timeout-truncate-and-provenance.md` records the round: what the report contained, what it did not, and the two items that remain open. Two of its findings are worth naming here because they change what the next round should do. FIRST, THE CRASH THE REPORT CALLS UNFIXED WAS FIXED IN 0.7.0, AND THE BUILD THAT WAS MEASURED DID NOT CONTAIN IT. The reported `OPENKAL_MUSL_TRACE` list names operations 88 and 324. Neither can be printed by 0.7.0: `symlink` and `symlinkat` have cases, `membarrier` has a case of its own whose stated purpose is that the trace not report it, and `trace_absent` is called from the default arm and nowhere else. Both numbers are defined for that architecture, so both cases are live. The measured library was 0.6.0, and since openkal-llvm-runtime 0.4.0 requires 0.7.0, the runtime was 0.3.1. SECOND, AND IT IS WHY THE FIRST COULD HAPPEN: A PROGRAM BUILT ON THIS PORT CANNOT STATE WHICH VERSION OF IT IT HOLDS. The only thing resembling a version a program can read is `SYS_uname`'s release field, which is the string literal "0.5.0" and has never moved. A consumer who checks it is told a version, and the version is wrong. §7 of the document proposes deriving that field from the package version and having the trace name the version before anything else; it is not done here, because it is a change to what a program observes and belongs in a release of its own rather than beside a repin.
…try does not cover
One row of this branch's first run reported
curl: (35) Recv failure: Connection reset by peer
##[error]Process completed with exit code 35
thirteen seconds in, before anything was built. `--retry` covers a transient
HTTP status and a timeout and does not cover a failure of the transport;
`--retry-all-errors` is the flag that does.
The same unprotected fetch is in openkal-linux, openkal-macos, openkal and
openkal-llvm-runtime, eight occurrences in total. They are left for changes of
their own rather than folded in here.
… to this port
The Windows row of the matrix answered
FAIL: setting a directory's modification time is refused, as EISDIR (errno=13)
and it was right to. This port opens the name as a file, because
`kal_fs_set_modified` takes a `kal_file` and openkal has no form of it taking a
directory or a name. What a backend says about opening a directory as a file is
the backend's: Linux distinguishes a directory and answers
`kal_err_is_directory`; the system with no such distinction answers
`kal_err_permission`, which is EACCES, which is 13.
So the refusal is asserted and the value is reported:
ok: a directory reports its modification time
note: refused with errno=21
ok: setting a directory's modification time is refused
Asserting the value would have made an observation about one implementation
while reading as an observation about the port -- which is the same mistake, in
a smaller place, as the one this round exists to fix. README.md's row now names
both values and says which is measured where, and the document records the
correction rather than being edited to look as if it had been right.
Measured after removing `target/` and building once: 47 observations, 0
failures. The first run of this was made against a stale artefact --- two
fingerprint directories existed, because the repin changed a dependency version,
and `find | head -1` answered with the older one. `tools/one-artifact.sh` exists
for exactly that and refuses rather than guessing; it is what should have been
used, and it is what produced the reading above.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Round four of mcpplibs/openkal-linux#13. Three of the reported items are answered, one diagnosis is corrected, and two remain open with the reasoning recorded.
truncate(76) had no caseOnly
ftruncatewas answered. The two are not interchangeable to a caller holding a name and no open file, which is every caller ofstd::filesystem::resize_file— libc++ expresses it asdetail::truncate(p.c_str(), size)(operations.cpp:966).Composed exactly as
SYS_utimensatbeside it is: resolve, open for reading and writing, act, release.Measured in
examples/posix, with the case and with it removed again:The descriptor is closed before those run, so neither can reach the operation that already worked.
The
EAGAINis real, and it is not herestd::filesystem::copy_fileansweringResource temporarily unavailablefor two ordinary files was openkal-linux and openkal-macos, fixed in 0.7.1 and 0.6.1, which this repins to.kal_timeout_readdecoded a borrowedkal_streamhandle with the decoder for an owned one, and waited upon the descriptor below the one it then transferred upon.This port is where it was observed: libc++ opens a copy's source with
O_NONBLOCK,do_readroutes that throughokm_timed_read,SYS_copy_file_rangeis ado_read/do_writeloop, andEAGAINis not in libc++'s fallback list. Nothing here needed to change — the route throughO_NONBLOCKwas correct and what it reached was not.The previous two rounds attributed this to exhaustion of the started-program table. That is now disposed of: it reproduces on a single copy of one ordinary file.
last_write_timeon a directory: the diagnosis is invertedThe report says reading a directory's modification time fails. It does not.
Reading is
stat→kal_fs_info→newfstatat, which resolves a directory. Setting isutimensat, and this port opens the name as a file becausekal_fs_set_modifiedtakes akal_fileand openkal has no form of it taking a directory or a name. A directory refuses that open, asEISDIR.libc++ names both overloads
"last_write_time"in the message it throws (operations.cpp:679and:691), so the text does not say which failed. A lock protocol built on a directory reads the timestamp to decide staleness and writes it to refresh the lock, and only the second fails.Measured, the two separated:
README.mdgains the row, and the refusal is asserted rather than merely not exercised — so the row is contradicted the day openkal gains the operation.The document
.agents/docs/2026-08-30-issue13-round-four-timeout-truncate-and-provenance.md. Two of its findings change what the next round should do:The crash the report calls unfixed was fixed in 0.7.0, and the build measured did not contain it. The reported trace names operations 88 and 324; neither can be printed by 0.7.0, because
symlink/symlinkathave cases,membarrierhas a case whose stated purpose is that the trace not report it, andtrace_absentis called from the default arm alone. Both numbers are defined for that architecture. The measured library was 0.6.0 — and since openkal-llvm-runtime 0.4.0 requires 0.7.0, the runtime was 0.3.1.A program built on this port cannot state which version it holds. The only version-shaped thing a program can read is
SYS_uname's release field, which is the literal"0.5.0"and has never moved. §7 proposes fixing that; it is deliberately not in this change, being a change to what a program observes.setpgid/setsidremain absent, and §6 argues they cannot be composed above the line and belong in a specification change: openkal's process interface has no group and no session, and the requirement behind them is ending a started program together with everything it started.