Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
629 changes: 629 additions & 0 deletions .agents/docs/2026-08-30-issue13-exec-search-and-what-it-hid.md

Large diffs are not rendered by default.

601 changes: 601 additions & 0 deletions .agents/docs/2026-08-30-issue13-full-sweep-design.md

Large diffs are not rendered by default.

28 changes: 24 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ jobs:
# --abort-terminated a termination rather than a return: Windows
# fail-fasts, and the fault this replaces was
# reported there as an exit
- { name: 'linux, gcc', os: ubuntu-24.04, toolchain: 'gcc@16.1.0', target: '', net: 'yes', fork: '--fork', shell: '--shell', abort: '--abort-signal' }
- { name: 'linux, llvm', os: ubuntu-24.04, toolchain: 'llvm@22.1.8', target: '', net: 'yes', fork: '--fork', shell: '--shell', abort: '--abort-signal' }
- { name: 'macos, llvm', os: macos-14, toolchain: 'llvm@20.1.7', target: '', net: 'yes', fork: '--fork', shell: '--shell', abort: '--abort-status' }
- { name: 'linux, gcc', os: ubuntu-24.04, toolchain: 'gcc@16.1.0', target: '', net: 'yes', fork: '--fork', shell: '--shell', abort: '--abort-signal', dirtime: '--dir-time' }
- { name: 'linux, llvm', os: ubuntu-24.04, toolchain: 'llvm@22.1.8', target: '', net: 'yes', fork: '--fork', shell: '--shell', abort: '--abort-signal', dirtime: '--dir-time' }
- { name: 'macos, llvm', os: macos-14, toolchain: 'llvm@20.1.7', target: '', net: 'yes', fork: '--fork', shell: '--shell', abort: '--abort-status', dirtime: '--dir-time' }
# ⚠️ openkal-windows declines `openkal.space`: this system has no
# primitive that copies an address space and starts a context in the
# copy, and inventing one would be the simulation clause 3.1
# forbids. `--no-fork` asserts the refusal.
- { name: 'windows, gcc', os: windows-2022, toolchain: 'gcc@16.1.0', target: 'x86_64-windows-gnu', net: 'yes', fork: '--no-fork', shell: '--no-shell', abort: '--abort-terminated' }
- { name: 'windows, gcc', os: windows-2022, toolchain: 'gcc@16.1.0', target: 'x86_64-windows-gnu', net: 'yes', fork: '--no-fork', shell: '--no-shell', abort: '--abort-terminated', dirtime: '--no-dir-time' }
defaults:
run:
shell: bash
Expand Down Expand Up @@ -628,6 +628,26 @@ jobs:
MCPP_TARGET: ${{ matrix.target }}
run: bash tools/run-probe.sh examples/subprocess subprocess ${{ matrix.fork }} ${{ matrix.shell }} ${{ matrix.abort }}

# ⭐⭐ WHETHER AN ANSWER IS TRUE, WHICH IS A DIFFERENT QUESTION FROM
# WHETHER AN OPERATION WORKS --- AND THE ONE NO PROBE HERE WAS ASKING.
#
# Every probe above starts something, opens something or transfers
# something and asserts that it happened. A whole family of defects lived
# underneath that: `fcntl(F_SETLK)' granted every lock and took none, so
# two programs held one exclusive lock; `F_GETLK' left the caller's word
# untouched, so the answer read "somebody holds this" for ever;
# `getppid' returned -38 AS AN IDENTIFIER; `sigaltstack' reported an
# installation it had not performed; `sysconf(_SC_OPEN_MAX)' answered 0
# for a bound this library sets itself.
#
# ⚠️ NOT ONE OF THEM IS A MISSING OPERATION, so not one appears in
# `OPENKAL_MUSL_TRACE=enosys'. They were found by comparing every answer
# against the host's, and this probe is that comparison kept.
- name: The surface probe
env:
MCPP_TARGET: ${{ matrix.target }}
run: bash tools/run-probe.sh examples/surface surface ${{ matrix.dirtime }}

# ⭐⭐ WHERE A STARTED PROGRAM'S OUTPUT WENT, WHICH THE PROBE CANNOT ASK
# ABOUT ITSELF.
#
Expand Down
37 changes: 27 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ the claim can be checked rather than repeated.

```toml
[dependencies]
openkal-musl = "0.9.0"
openkal-musl = "0.10.0"
```

It names no implementation and no platform: a C library is the one consumer that
Expand Down Expand Up @@ -43,8 +43,8 @@ architecture. Replacing that one header is the whole of the redirection; the

`musl/PATCHES.md` lists the whole of what is not unmodified: **four patched
lines**, all of one kind — a machine word carried through a variable declared
`long`, which is not a machine word on one of the three targets — and **nine
replaced sources**. Five of the nine are replaced for the same reason: each
`long`, which is not a machine word on one of the three targets — and **ten
replaced sources**. Five of the ten are replaced for the same reason: each
reads the shape of one particular environment rather than asking a kernel for
something.

Expand All @@ -62,6 +62,12 @@ a separator — correct on every system musl was written for, and not on one tha
writes a volume first — and one walks the program headers of an image in a format
two of the three targets do not have.

The tenth is replaced because the fifth was. `src/process/posix_spawnp.c` does
not search a PATH itself: it hands `__execvpe` to `posix_spawn` to be run **in
the duplicate**, and this port has no duplicate to run it in, so the field was
read by nobody and a name without a separator was taken as a path. It reported
success for a program it had not started.

Four names have no C in musl to fall back on — `setjmp`, `longjmp`,
`sigsetjmp` and the cancellable system-call sequence. The first three are
supplied by one assembly file that assembles for every object format; the
Expand Down Expand Up @@ -119,7 +125,13 @@ answer that leaves a program wrong without telling it.
| closing a standard stream in a program being started | `posix_spawn_file_actions_addclose(&fa, 0…2)` makes the spawn report `ENOSYS`; above position two it is performed, because nothing there is inherited | openkal has no value meaning "no stream", and the value that looks like one — zero — means the opposite: the stream the caller has. Accepting the action and not performing it would hand a program the standard input its caller had just taken away. |
| starting a program upon a stream whose handle is zero | a caller that redirects its **output** onto its own standard input and then starts a program gets `ENOSYS` | `kal_spawn_streams` reserves zero for inheritance and `kal_stream` reserves nothing, so an implementation whose streams are the environment's own descriptors hands out zero for standard input. The two agree at position zero and cannot be told apart anywhere else. Reported upstream; refused here rather than answered wrongly. |
| ~~a version a program can read~~ | **answered since 0.9.0.** `uname`'s `release` field is this package's version, and `OPENKAL_MUSL_TRACE=enosys` names it on the error stream once per process before the program runs | It was the string literal `0.5.0` through every release after 0.5.0, so a program that asked was not left without an answer -- it was given a false one. ⚠️ It therefore MOVES AT EVERY RELEASE: nothing here or in musl reads it (`gethostname` and `getdomainname` are musl's only consumers of `uname` and both read `nodename`), but a program above it that compares the field against a fixed string will see it change. `sysname` is `openkal` and not `Linux`, so nothing can have been reading it as a kernel version. |
| **setting** the modification time of a directory | `utimensat` on a directory is refused, so `std::filesystem::last_write_time(dir, t)` throws. **Reading** it is unaffected and correct. The value differs by implementation and is the implementation's to give: `EISDIR` on Linux and macOS, `EACCES` on Windows — both measured | `kal_fs_set_modified` takes a `kal_file` and openkal has neither a `kal_dir` form of it nor a form that takes a name, so this port opens the name as a file, which a directory refuses. What a backend says about that is its own: one distinguishes a directory and one does not. Note that libc++ gives both overloads of `last_write_time` the same name in the message it throws, so the text does not say which of the two failed: the reading overload is `stat` and works on a directory. A caller using a lock directory's timestamp reads it to decide staleness and writes it to refresh the lock, and only the second fails. |
| ~~**setting** the modification time of a directory~~ | **answered since 0.10.0 where the implementation can open a directory**, which Linux and macOS can and Windows cannot — its `kal_fs_open` names `FILE_NON_DIRECTORY_FILE`, so `utimensat` on a directory is still refused there. **Reading** it was never affected and is correct everywhere | the port used to ask for `KAL_OPEN_READ \| KAL_OPEN_WRITE` unconditionally, which a directory refuses; it now asks what the name refers to and opens a directory for reading only. ⚠️ **That is outside what `fs.h` states** — the interface requires `KAL_OPEN_WRITE` for `kal_fs_set_modified` and names `kal_fs_open_dir`, which yields a `kal_dir`, as the way to open a directory, while `kal_fs_set_modified` has no `kal_dir` form. So there is no stated route to a directory's time at all; one has been asked for. A file still asks for exactly what the interface requires. Note that libc++ gives both overloads of `last_write_time` the same name in the message it throws, so a failure did not say which of the two had failed — and the one that worked was the one a consumer reported as broken. |
| ~~a lock on a file~~ | **`fcntl(F_SETLK)`, `F_SETLKW` and `F_GETLK` report `ENOSYS` since 0.10.0.** ⚠️ They used to answer 0 and do nothing, so **two programs took one exclusive lock and both were told they had it**; `F_GETLK` left the caller's word untouched, which reads as "somebody holds this" — for ever, so a loop waiting for a lock to be released never left it. `flock` has no case and reports `ENOSYS` too | openkal has no locking operation. ⭐ **Unlike the permission row, this refusal is temporary**: `fcntl(F_SETLK)` on Linux and macOS and `LockFileEx` on Windows all exist and all take a byte range, so every environment beneath openkal can perform it — what is missing is a word in the specification, and one has been asked for (a `kal_fs_lock` beside a `kal_fs_props` position, admitted on exactly the grounds the link operations were). It cannot be composed here meanwhile: a lock built from `KAL_OPEN_EXCLUSIVE` and a name beside the file is released by nobody when its holder dies, so a program that ended abnormally while holding one would be locked out of its own file for ever. |
| whether a file may be executed | `access(path, X_OK)` answers **yes for anything that exists**, and starting a name that exists and cannot be run still ends the caller with 127 | `kal_node_info` carries `writable` and no other permission, so "it is there" is the whole of what this port can answer. The two halves are the same gap: the enquiry cannot tell, and neither can the check `posix_spawn` makes before starting. openkal-linux knows — its own duplicate is the thing that fails — and has been asked to report it. |
| descriptors above 2 crossing into a started program | a started program receives standard input, output and error and **nothing else**; a non-close-on-exec descriptor 4 is not there, and `fcntl(F_SETFD, 0)` upon one therefore changes nothing | `kal_spawn_streams` has exactly three positions and openkal has no general form for placing a stream at position *n*. `posix_spawn_file_actions_adddup2` above position two is already refused rather than accepted, so the two agree; only implicit inheritance is lost. A general form has been asked for. |
| how many processors there are | `sched_getaffinity` reports `ENOSYS`, so `std::thread::hardware_concurrency()` and `sysconf(_SC_NPROCESSORS_ONLN)` answer **1** | ⚠️ this one is silent: a program sizing a pool of workers gets one worker and no error. `openkal.task` says whether contexts run in parallel (`KAL_TASK_PROP_PARALLEL`) and not how many can; an enquiry has been asked for beside that word. |
| volume capacity, hard links, named pipes | `statvfs` (`std::filesystem::space`), `link` (`create_hard_link`), `mkfifo` and `socketpair` report `ENOSYS` | openkal has no operation for any of them. `kal_fs_link_create` makes a node whose content is a name — a symbolic link — and there is no hard link; `kal_process_channel` is a pipe in one direction, so a bidirectional pair is not one of them. Each is a loud absence rather than a wrong answer, which is why none is composed here. |
| an alternate signal stack | `sigaltstack` reports `ENOSYS` since 0.10.0 | it used to report success and install nothing, and the enquiry that would have caught it answered 0 with a zeroed record. There are no signals here, so there is nothing for such a stack to be. |

**⭐ What carries confinement here, since a mode word does not.** A program that
writes "only I may read this" as a mode is stating it in a vocabulary this
Expand Down Expand Up @@ -182,7 +194,7 @@ spent on exactly that question.
Each operation the dispatcher has no case for is then named on the standard
error stream, **once**, whatever the number of attempts:

openkal-musl 0.9.0
openkal-musl 0.10.0
openkal-musl: no operation for system call 266

**The first line is the version, and it is printed whether or not anything is
Expand Down Expand Up @@ -331,13 +343,18 @@ absent**, on the reading that clause 7.1 declines to duplicate an address space
**pair**. `openkal.space` supplies the first half by itself, and what was
missing was never an atom.

**`execve` is starting a program, waiting for it, and ending with its status.** A
caller cannot distinguish that through this library: the same program runs, with
the same arguments, on the same streams, and the same status reaches whoever
waits. There are two images where a system with the operation would have one. It
is what every environment without the operation does, and two of the three
**`execve` is starting a program, waiting for it, and ending with its status.**
It is what every environment without the operation does, and two of the three
beneath openkal are such environments.

⚠️ **This paragraph used to add that a caller cannot distinguish it. A caller
can, and saying otherwise is what kept anyone from looking.** Three differences
are known and `musl/PATCHES.md` states each: a program that cannot be started
(**answered since 0.10.0** — the name is asked about first, so `execvp` can
search a PATH), a `kill` that reaches the waiting copy rather than the program
(**not answered**; use `posix_spawn`, `system` or `popen` where a caller needs
to stop what it started), and the identifier the started program reports.

**A program named without a suffix** is tried with one environment's suffix
second, which is what every C library for that environment does. It is here
rather than beneath because openkal is deliberately literal about names: it
Expand Down
42 changes: 13 additions & 29 deletions examples/posix/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,43 +123,27 @@ int main(int argc, char **argv, char **envp) {
if (d) closedir(d);
check(entries == 2, "the directory reports both entries");

/* READING A DIRECTORY'S MODIFICATION TIME WORKS AND SETTING IT DOES NOT,
* AND THE TWO ARE OBSERVED SEPARATELY BECAUSE THE C++ LIBRARY ABOVE GIVES
* THEM ONE NAME.
/* READING A DIRECTORY'S MODIFICATION TIME WORKS, AND IT IS OBSERVED HERE
* SEPARATELY FROM SETTING IT BECAUSE THE C++ LIBRARY ABOVE GIVES THEM ONE
* NAME.
*
* libc++ throws `filesystem error: in last_write_time' for both overloads,
* so a caller that reads a lock directory's timestamp to decide staleness
* and writes it to refresh the lock cannot tell from the message which of
* the two failed. It was read as the first in openkal-linux#13 and it is
* the second: `stat' resolves a directory perfectly well, while
* `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.
* the two failed. It was reported as the first in openkal-linux#13 and it
* was the second: `stat' resolves a directory perfectly well.
*
* The refusal is asserted rather than merely not exercised. README.md
* carries the row; if openkal gains the operation, this is the observation
* that says the row is out of date. */
* ⭐ SETTING IT USED TO BE ASSERTED HERE AS A REFUSAL, and this comment said
* that if openkal gained the operation this observation would be the one to
* say the row was out of date. It did its job: 0.10.0 opens a directory for
* READING to stamp it --- which Linux and macOS perform and Windows does not
* --- so the answer now depends on the implementation beneath, and an
* observation that depends on the backend belongs where the backend is
* stated on the command line. It moved to `examples/surface', under
* `--dir-time | --no-dir-time'. Only the reading half is left here. */
struct stat ds;
check(stat("okm-probe.dir", &ds) == 0 && S_ISDIR(ds.st_mode) && ds.st_mtime > 0,
"a directory reports its modification time");
{
struct timespec ts[2];
ts[0].tv_sec = 0; ts[0].tv_nsec = UTIME_OMIT;
ts[1].tv_sec = 1700000000; ts[1].tv_nsec = 0;
errno = 0;
const int r = utimensat(AT_FDCWD, "okm-probe.dir", ts, 0);
/* THE REFUSAL IS THIS PORT'S AND THE VALUE IS THE BACKEND'S, so the
* refusal is asserted and the value is reported.
*
* This asserted EISDIR and one row of the matrix answered 13 --- EACCES.
* Both are correct: the port opens the name as a file, and what a
* backend says about opening a directory as one is its own. Linux
* answers kal_err_is_directory and the system with no such distinction
* answers kal_err_permission. Asserting the first would have made this
* an observation about one implementation while claiming to be about
* the port. */
printf("note: refused with errno=%d\n", errno);
check(r != 0, "setting a directory's modification time is refused");
}

unlink("okm-probe.dir/a"); unlink("okm-probe.dir/b");
check(rmdir("okm-probe.dir") == 0, "the directory is removed");
Expand Down
Loading
Loading