Skip to content

Commit e51e0b1

Browse files
authored
0.13.1 — the probe now starts a program that needs an interpreter (#30)
And the README paragraph that blamed the emulator is corrected, because the way it was wrong matters more than the fact. `examples/subprocess` starts a `#!` script and asserts it runs. ⚠️ The script is made executable BY THE SHELL, not by this program: `chmod` is refused here, so a script this port wrote would be refused for its MODE and the observation would have held for the wrong reason — which is exactly what the first attempt at this check did, answering EACCES and looking like the defect being hunted. openkal-linux 0.12.0 carries the fix: `execveat` with a directory descriptor hands the interpreter `/dev/fd/<dirfd>/<name>`, which a close-on-exec descriptor does not survive.
1 parent 84d2dae commit e51e0b1

3 files changed

Lines changed: 100 additions & 22 deletions

File tree

README.md

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -348,29 +348,49 @@ was measuring in the first place, and the growth does not change its direction.
348348

349349
## Verification
350350

351-
### ⚠️ Measuring a foreign architecture through emulation, and the one thing it cannot show
351+
### ⚠️⚠️ A program that needs an INTERPRETER could not be started, and it was first blamed on the emulator
352352

353-
An `aarch64` build of this library runs on an `x86_64` machine through
354-
`qemu-user` and `binfmt_misc`, and every observation in this repository's probes
355-
holds there **except the ones that start a program built for the same foreign
356-
architecture**. That is a property of the emulator and not of this library, and
357-
it is stated here because the failure looks exactly like a defect:
353+
**Corrected in openkal-linux 0.12.0.** An earlier version of this section said
354+
that an `aarch64` build measured through `qemu-user` could not start a program of
355+
its own architecture, and that this was a property of the emulator. **That was
356+
wrong**, and the way it was wrong is worth more than the fact:
358357

359-
| what a program starts | under emulation |
358+
- the symptom appeared only on `aarch64`, where *every* foreign binary needs the
359+
`binfmt_misc` interpreter, so *every* start failed at once;
360+
- the release before it failed identically, which was read as "pre-existing,
361+
therefore not ours";
362+
- a consumer's 108 tests passed on both architectures, which was read as "not
363+
reaching users".
364+
365+
⇒ Each of those was true. The conclusion drawn from them was not.
366+
367+
**What it actually was.** `execveat` with a directory descriptor and a relative
368+
name gives the kernel the program's name as `/dev/fd/<dirfd>/<name>`. For an
369+
ordinary executable that spelling never surfaces — the kernel holds the file open
370+
already. For a program that needs an **interpreter** it does: the kernel starts
371+
the interpreter and hands it that name *to open*, after the replacement, by which
372+
time a close-on-exec descriptor is gone. The interpreter is told the file does
373+
not exist.
374+
375+
⭐ Isolated in twenty lines of ordinary C, with nothing of openkal in it:
376+
377+
```
378+
dirfd WITH O_CLOEXEC execveat -> ENOENT
379+
dirfd WITHOUT O_CLOEXEC STARTED ok
380+
```
381+
382+
⚠️ **It was never about architecture.** Two kinds of program need an interpreter,
383+
and both were refused on every system:
384+
385+
| program | needs an interpreter |
360386
| --- | --- |
361-
| a native binary of the host — `sh`, `git`, anything on `PATH` | works |
362-
| **another copy of the foreign-architecture program itself** | the start fails |
363-
364-
⭐ The distinction is the second exec. The kernel runs a native binary directly,
365-
and the emulated process simply stops being emulated; a foreign one has to be
366-
re-entered through `binfmt_misc` from inside an already-emulated process, which
367-
`qemu-user` does not do. Measured: `execveat` returns `ENOENT` for a path that
368-
exists, while the same call in the same run starts `/bin/sh` correctly, and the
369-
same failure appears on the release before this one.
370-
371-
**A consumer measuring itself this way is not affected** unless its tests start
372-
copies of themselves. A suite that runs helper programs sees nothing wrong: one
373-
consumer's 108 tests pass identically on both architectures.
387+
| a `#!` script | yes — on **every** architecture, including a plain x86_64 machine |
388+
| a binary of another architecture, through `binfmt_misc` | yes — which is why `aarch64` showed it first |
389+
| an ordinary native executable | no — which is why nothing else failed |
390+
391+
⇒ The emulator only made it *visible*. A consumer running shell scripts on real
392+
`aarch64` hardware — or on x86_64 — met the same refusal. `examples/subprocess`
393+
now starts a script the shell made executable, and asserts it runs.
374394

375395
`examples/wordcount` is an ordinary POSIX program whose source mentions nothing
376396
of any of this. Its three counts are compared against the system's own `wc`,

examples/subprocess/src/main.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,64 @@ int main(int argc, char** argv)
798798
}
799799
}
800800

801+
/* --- a program that needs an INTERPRETER ---------------------------------
802+
*
803+
* ⚠️⚠️ A WHOLE CLASS OF PROGRAMS COULD NOT BE STARTED, AND NOTHING HERE
804+
* LOOKED. `execveat' with a directory descriptor and a relative name gives
805+
* the kernel the program's name as `/dev/fd/<dirfd>/<name>'. For an ordinary
806+
* executable that spelling never surfaces --- the kernel holds the file open
807+
* already. For a program that needs an INTERPRETER it does: the kernel starts
808+
* the interpreter and hands it that name TO OPEN, after the replacement, by
809+
* which time a close-on-exec descriptor is gone. The interpreter is told the
810+
* script does not exist.
811+
*
812+
* ⭐ Two kinds of program need one, and they are the same defect:
813+
* a `#!' script --- on every architecture
814+
* a binary of another architecture --- through `binfmt_misc'
815+
*
816+
* ⚠️ IT WAS FOUND ON aarch64 AND FIRST BLAMED ON THE EMULATOR, because there
817+
* every foreign binary needs the binfmt interpreter and so every start
818+
* failed at once. It reproduces natively with a script, which is what a
819+
* consumer meets on an ordinary machine.
820+
*
821+
* ⚠️ The script is made executable by the SHELL and not by this program:
822+
* `chmod' is refused here, so a script this program wrote would be refused
823+
* for its mode and the observation would hold for the wrong reason. */
824+
if (expect_shell) {
825+
char here[512];
826+
if (!getcwd(here, sizeof here)) here[0] = 0;
827+
char script[640], mk[900];
828+
snprintf(script, sizeof script, "%s/interp-probe.sh", here);
829+
snprintf(mk, sizeof mk,
830+
"printf '#!/bin/sh\\nexit 0\\n' > %s && chmod 755 %s", script, script);
831+
832+
pid_t m = -1;
833+
char* mav[] = { (char*)"sh", (char*)"-c", mk, NULL };
834+
int ms = 0;
835+
const int me = posix_spawnp(&m, "sh", NULL, NULL, mav, environ);
836+
if (me == 0) waitpid(m, &ms, 0);
837+
/* The control: unless the shell really made an executable script, the
838+
* observation below would fail for a reason that is not the one sought. */
839+
check(me == 0 && WIFEXITED(ms) && WEXITSTATUS(ms) == 0 &&
840+
access(script, X_OK) == 0,
841+
"a shell makes an executable script for this program to start");
842+
843+
pid_t s = -1;
844+
char* sav[] = { script, NULL };
845+
int ss = 0;
846+
errno = 0;
847+
const int se = posix_spawn(&s, script, NULL, NULL, sav, environ);
848+
if (se != 0) {
849+
printf("note: starting the script reported errno %d\n", se);
850+
check(0, "a program that needs an interpreter starts");
851+
} else {
852+
waitpid(s, &ss, 0);
853+
check(WIFEXITED(ss) && WEXITSTATUS(ss) == 0,
854+
"a program that needs an interpreter starts, and runs");
855+
}
856+
unlink(script);
857+
}
858+
801859
/* --- units, which 0.12.0 added and no probe here ever looked at ------------
802860
*
803861
* ⚠️⚠️ THE WHOLE OF `setpgid'/`kill(-n)' SHIPPED WITH ITS ONLY WITNESS IN

mcpp.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-musl"
4-
version = "0.13.0"
4+
version = "0.13.1"
55
description = "musl 1.2.5 redirected onto openkal: one C library, ported once, above every implementation of the specification rather than above one kernel."
66
license = "Apache-2.0"
77

@@ -30,7 +30,7 @@ openkal = "0.12.0"
3030
#
3131
# The consequence for a program is that it names this package and nothing else.
3232
[target.'cfg(os = "linux")'.dependencies]
33-
openkal-linux = { version = "0.11.0", features = ["standalone"] }
33+
openkal-linux = { version = "0.12.0", features = ["standalone"] }
3434

3535
[target.'cfg(os = "macos")'.dependencies]
3636
openkal-macos = { version = "0.9.0", features = ["standalone"] }

0 commit comments

Comments
 (0)