0.10.0 — the exec search, and the family of answers that were not true - #22
Merged
Merged
Conversation
openkal-linux#13, fifth round. A consumer measured that `execvp` could not get
past the first PATH entry that missed, and that one libc path accounted for nine
of their nineteen failures. It is fixed --- and reading it out found six more of
the same family, one of which was worse than the one reported.
⭐⭐ WHAT THEY HAVE IN COMMON, AND WHY NO DIAGNOSTIC HERE COULD SEE THEM.
Every probe in this repository asked whether an operation WORKED. None asked
whether an answer was TRUE. `OPENKAL_MUSL_TRACE=enosys` reports what reaches the
default arm, and not one of these reaches it: they are operations that are
present and answer wrongly. `examples/surface` is the probe that asks the other
question, by comparing every answer against the host's.
--- what a consumer reported -----------------------------------------------
`execve` did not return when the program could not be started. The replacement
happens inside a duplicate, whose failure openkal-linux reports to nobody, so
the duplicate ended with 127 and `execve` ended the CALLER with it. musl's
`execvp` issues one `execve` per PATH entry and needs each to return.
⇒ `__posix_spawn` asks whether the name can be started before starting it.
ENOENT and EACCES reach the caller; a name that exists and cannot be
executed still does not, because openkal reports no execute permission ---
recorded, and asked of openkal-linux, which knows and does not report it.
`posix_spawnp` never searched a PATH at all. musl hands `__execvpe` to
`posix_spawn` to run in the duplicate; this port has no duplicate, so the field
was read by nobody and `posix_spawnp("sh", ...)` started `./sh` and REPORTED
SUCCESS. musl's source is now replaced, the tenth, and the search is performed
here by musl's own rules.
--- what reading it out found ----------------------------------------------
⚠️ ⚠️ A LOCK WAS GRANTED AND NEVER TAKEN. `fcntl(F_SETLK)`, `F_SETLKW` and
`F_GETLK` answered 0 and did nothing. Measured against the host: two programs
took one exclusive lock and BOTH were told they had it. `F_GETLK` pointed the
other way --- it left the caller's word untouched, which reads as "somebody
holds this", for ever, so a loop waiting for a lock never left it. All three
report ENOSYS.
⭐ The refusal is temporary in a way the permission one is not: every
environment beneath openkal can lock a byte range, and what is missing is a
word in the specification. Composing one here from KAL_OPEN_EXCLUSIVE is not
an option --- nothing would release it when its holder died.
⚠️ `getppid` RETURNED -38 AS AN IDENTIFIER, which is the defect `getpgrp` had one
release earlier, three lines away in the same dispatch, and it was not looked
for. The criterion added here asks the whole family.
A copy made by `fork` reported its parent's identifier: `getpid` answered the
constant 1 in every context. It is settled before the copy is taken and carried
into it --- and the comparison `kill` makes to decide "this program itself"
moved with it, or `abort` in a copy would have reported ESRCH.
`setpgid(0, 0)` and `setsid` refused a question neither asks. Being in a group of
one already holds here, and `setsid` has a failure POSIX writes down for that
state. 0 and EPERM. Every daemonising library handles EPERM; none handles ENOSYS.
`sigaltstack` reported an installation it had not performed, and the enquiry
answered 0 with a zeroed record. ENOSYS.
`sysconf(_SC_OPEN_MAX)` answered 0 for a bound this library sets itself.
`utimensat` could not set a DIRECTORY's time, which a consumer reported as a
failure to READ one --- libc++ gives both overloads of `last_write_time` one
name. Reading was never broken. The port asked for READ|WRITE unconditionally;
it now asks what the name refers to. ⚠️ Outside what fs.h states, recorded, and
asked of the specification.
--- what is NOT fixed, and is now recorded ---------------------------------
`kill` does not reach a program started by `fork` + `execve`: it reaches the
copy that is waiting for it. Measured with the host as control --- identical
status words, opposite outcomes. openkal has no way to say "this program's
lifetime is bound to mine", and `kal_process_terminate` is right to terminate
only what it was given. Asked of the specification. README says to use
`posix_spawn`, `system` or `popen` where a caller needs to stop what it started.
Also recorded: `access(X_OK)` answers yes for anything that exists; descriptors
above 2 do not cross into a started program; `sched_getaffinity` is absent so
`hardware_concurrency()` answers 1 SILENTLY; `statvfs`, `link`, `mkfifo` and
`socketpair` have no operation beneath.
--- criteria ---------------------------------------------------------------
`examples/surface` is new: eleven of its observations fail on 0.9.0 and none
does here. `examples/subprocess` gains the exec search and the identity, and
on 0.9.0 it does not merely fail --- it STOPS, at the `execve` observation,
which is the defect itself.
⭐ Two of them are guards rather than criteria: they hold before this change as
well. `abort` in a copy is still SIGABRT, and a failed `fork` gives its table
entry back. Listing them beside the criteria would have made five of seven
observations look like results.
⚠️ `examples/posix` asserted that setting a directory's time IS refused, and its
own comment said that observation would be the one to say the row was out of
date. It was. The half that depends on the backend moved to `examples/surface`,
under `--dir-time`, and the reading half stayed.
⚠️ ⚠️ `posix_spawnp' IS THE TENTH REPLACED SOURCE AND TWO OTHER LISTS SAY WHAT THE FIRST ONE SAYS. `tools/probe-cross-macos.sh' and `tools/cross-build-macos.sh` each keep their own copy of musl's source selection, because the linker they drive does not read mcpp.toml. Both matched `posix_spawn' anchored on the whole basename, so neither covered `posix_spawnp.c', and the cross-link reported ld64.lld: error: duplicate symbol: _posix_spawnp ⭐ Found by the job whose whole purpose is to notice that a second statement has fallen behind the first --- on the first release that made it fall behind. The warning is now written beside the entry rather than left to be rediscovered. --- and two the reading found ----------------------------------------------⚠️ AN ENQUIRY THAT CANNOT BE MADE IS NOT AN ANSWER OF `NO'. `startable' and the directory branch of `utimensat' both ask `kal_fs_info' first, and a build configured without `openkal.fs' --- OKM_HAS_FS=0, which a machine with no storage uses --- answers `not supported'. Turning that into a refusal would have stopped a spawn this port would otherwise have attempted. Both now fall through to the operation that follows, which answers as it did before the enquiry existed. `__okm_self_pid' was written and never called. Removed. And `slot`/`reserved_pid` in `__okm_fork' are `volatile`. Both are written BEFORE the `setjmp' and read only on the path that does not resume through it, so this is not a correctness fix --- it is this file's own rule, which is that a local live across that call says so rather than leaving a reader to reconstruct which path reads which.
…use it⚠️ One environment separates its own PATH with a semicolon and begins each entry with a volume letter and a colon, so reading that PATH on a colon produces entries that are not names. It is still a colon, because `execvp' --- which this port does NOT replace, and which reaches `execve' --- splits on one in musl's own source. Splitting differently in `__posix_spawnp' would make the two ways of searching for one program disagree with each other, which is worse for a caller to meet than one way both are wrong in. Recorded rather than quietly differing; nothing on that target searches a PATH today, because its row declares no shell.
⚠️ `getenv' answers a pointer INTO the environment and the `setenv' that follows may move it, so the PATH the probe replaces has to be kept somewhere. It was kept in 1024 bytes and restored from them --- and a continuous-integration machine's PATH is longer than that, so what would have been restored is a TRUNCATED PATH: the probe quietly corrupting the environment for whatever observation is added after it. Kept by the value's own length instead. Found by re-reading the diff rather than by anything failing, which is what it would have done --- later, and to someone else's observation.
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.
openkal-linux#13, fifth round.
A consumer measured that
execvpcould not get past the first PATH entry thatmissed, and that one libc path accounted for nine of their nineteen failures
plus four sections silently skipped (
bwrap, installed at/usr/bin/bwrap,reported as not installed). That is fixed — and reading it out found six more
of the same family, one of which is worse than the one reported.
⭐⭐ What they have in common, and why no diagnostic here could see them
Every probe in this repository asked whether an operation worked. None asked
whether an answer was true.
OPENKAL_MUSL_TRACE=enosysreports what reachesthe default arm, and not one of these reaches it — they are operations that are
present and answer wrongly.
examples/surfaceis the probe that asks the other question, by comparing everyanswer against the host's. It is how the six were found.
Reported by the consumer
execvedid not return when the program could not be startedexecveended the caller with it.__posix_spawnnow asks whether the name can be started firstposix_spawnpnever searched a PATH at all__execvpetoposix_spawnto run in the duplicate; this port has no duplicate, so the field was read by nobody andposix_spawnp("sh", …)started./shand reported success. musl's source is now replaced — the tenthfcntl(F_SETLK),F_SETLKWandF_GETLKanswered 0 and did nothing. Measuredagainst the host: two programs took one exclusive lock and both were told they
had it.
F_GETLKpointed the other way — it left the caller's word untouched,which reads as "somebody holds this", for ever, so a loop waiting for a lock
never left it. All three report
ENOSYS.⭐ The refusal is temporary in a way the permission one is not: every
environment beneath openkal can lock a byte range (
fcntl,LockFileEx), andwhat is missing is a word in the specification. Composing one here from
KAL_OPEN_EXCLUSIVEis not an option — nothing would release it when its holderdied.
The rest
getppidreturned -38 as an identifier — the defectgetpgrphad onerelease earlier, three lines away in the same dispatch, not looked for. The
criterion added here asks the whole family.
forkreported its parent's identifier. Settled before thecopy is taken and carried into it — and the comparison
killmakes moved withit, or
abortin a copy would have reportedESRCH.setpgid(0,0)andsetsidrefused a question neither asks →0andEPERM.Every daemonising library handles
EPERM; none handlesENOSYS.sigaltstackreported an installation it had not performed →ENOSYS.sysconf(_SC_OPEN_MAX)answered 0 for a bound this library sets itself.utimensatcould not set a directory's time — reported as a failure toread one, because libc++ gives both overloads of
last_write_timeone name.Reading was never broken.
What is NOT fixed, and is now recorded
killdoes not reach a program started byfork+execve— it reaches thecopy that is waiting for it. Measured with the host as control: identical status
words, opposite outcomes. openkal has no way to say "this program's lifetime is
bound to mine", and
kal_process_terminateis right to terminate only what itwas given. Asked of the specification; README says to use
posix_spawn,systemorpopenwhere a caller needs to stop what it started.README.mdandmusl/PATCHES.mdclaimed a caller cannot distinguishexecve-as-spawn+wait from a real replacement. That claim was false and is whatkept anyone from looking. Both now list the three known differences.
Criteria
examples/surfaceis new: eleven of its observations fail on 0.9.0 and nonedoes here.
examples/subprocessgains the exec search and the identity. On 0.9.0 it doesnot merely fail — it stops, at the
execveobservation, which is thedefect itself.
abortin a copy is still SIGABRT, and a failedforkgives its table entryback. Listing them beside the criteria would have made five of seven
observations look like results.
examples/posixasserted that setting a directory's time is refused, andits own comment said that observation would be the one to say the row was out
of date. It was.