Two things, and the second is why the first matters.
1. The claim that execve-as-spawn+wait is indistinguishable is false
openkal-musl expresses execve as starting a program, waiting for it, and
ending with its status — clause 3.1 declines to simulate replacing an image, and
that is right. Both README.md and musl/PATCHES.md in that repository added
that "a caller cannot distinguish that through this library".
⚠️ A caller can, and the claim is what kept anyone from looking. Three
differences are now known; two were found by a consumer, not by us:
- When the program cannot be started. The replacement fails inside the
duplicate, which ends with 127, so execve ended the caller with 127
instead of returning -1. musl's execvp needs each execve to return so it
can try the next PATH entry — so a program named without a separator was
found only if it happened to sit in the first entry. (Answered in
openkal-musl 0.10.0 by asking kal_fs_info first; the residue is below.)
kill does not reach the program. This is what this issue asks about.
- The identifier the started program reports is not the caller's.
2. kill reaches the waiter, not the program
After fork + execve there are three images, not two: the copy is left
waiting for the program it started. A signal sent to the identifier the parent
holds reaches the waiter.
Measured, with the host as control — identical status words, opposite
outcomes:
status what actually happened
openkal-musl 0x000f the program RAN TO COMPLETION, unsupervised
host glibc 0x000f the program was killed
The parent is told the program died on the signal it sent. It did not.
⭐ kal_process_terminate is not at fault. It was asked to terminate one
started program and it did exactly that. What is missing is a way to say the
thing execve means: this program exists only to stand in for me, so its
lifetime is bound to mine.
The shape asked for
A spawn that states the binding — a flag on kal_spawn_streams' sibling
argument, or a kal_process_spawn_bound, whichever fits clause 8. Backends:
|
|
| Linux |
prctl(PR_SET_PDEATHSIG, SIGKILL) in the started child |
| Windows |
a job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
| macOS |
no PDEATHSIG; a kqueue NOTE_EXIT watch, or the property is not claimed |
Declaring it optional (a kal_process_props position) is consistent with how
KAL_PROCESS_PROP_STREAM_PASSING is already handled: the port asks only where a
caller actually needs it.
An alternative that was evaluated and rejected
Making kal_process_terminate kill a process group, with kal_space_start
putting each copy in one. It works for this case and breaks another: a new
process group leaves the terminal's foreground group, so in a program with a
terminal interface any child that reads the terminal takes SIGTTIN and stops.
That trades a visible defect for a much harder one.
Meanwhile
openkal-musl 0.10.0 records the divergence in both files, replaces the false
claim, and tells consumers to use posix_spawn, system or popen where they
need to stop what they started — kill reaches the program on all three of
those, which is measured.
Two things, and the second is why the first matters.
1. The claim that
execve-as-spawn+wait is indistinguishable is falseopenkal-muslexpressesexecveas starting a program, waiting for it, andending with its status — clause 3.1 declines to simulate replacing an image, and
that is right. Both
README.mdandmusl/PATCHES.mdin that repository addedthat "a caller cannot distinguish that through this library".
differences are now known; two were found by a consumer, not by us:
duplicate, which ends with 127, so
execveended the caller with 127instead of returning -1. musl's
execvpneeds eachexecveto return so itcan try the next PATH entry — so a program named without a separator was
found only if it happened to sit in the first entry. (Answered in
openkal-musl 0.10.0 by asking
kal_fs_infofirst; the residue is below.)killdoes not reach the program. This is what this issue asks about.2.
killreaches the waiter, not the programAfter
fork+execvethere are three images, not two: the copy is leftwaiting for the program it started. A signal sent to the identifier the parent
holds reaches the waiter.
Measured, with the host as control — identical status words, opposite
outcomes:
The parent is told the program died on the signal it sent. It did not.
⭐
kal_process_terminateis not at fault. It was asked to terminate onestarted program and it did exactly that. What is missing is a way to say the
thing
execvemeans: this program exists only to stand in for me, so itslifetime is bound to mine.
The shape asked for
A spawn that states the binding — a flag on
kal_spawn_streams' siblingargument, or a
kal_process_spawn_bound, whichever fits clause 8. Backends:prctl(PR_SET_PDEATHSIG, SIGKILL)in the started childJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEPDEATHSIG; akqueueNOTE_EXITwatch, or the property is not claimedDeclaring it optional (a
kal_process_propsposition) is consistent with howKAL_PROCESS_PROP_STREAM_PASSINGis already handled: the port asks only where acaller actually needs it.
An alternative that was evaluated and rejected
Making
kal_process_terminatekill a process group, withkal_space_startputting each copy in one. It works for this case and breaks another: a new
process group leaves the terminal's foreground group, so in a program with a
terminal interface any child that reads the terminal takes
SIGTTINand stops.That trades a visible defect for a much harder one.
Meanwhile
openkal-musl0.10.0 records the divergence in both files, replaces the falseclaim, and tells consumers to use
posix_spawn,systemorpopenwhere theyneed to stop what they started —
killreaches the program on all three ofthose, which is measured.