Releases: mcpplibs/openkal-musl
Release list
0.13.5
A large allocation is a mapping, and a mapping is whole pages. musl uses a mapping to the end of its last page; the port obtained only the length asked for, so on Windows an allocation of 131,052 bytes or more wrote up to a page past its heap block and the program stopped at a later free or allocation. Consumers reach this release through openkal-llvm-runtime = "0.9.6".
0.13.4
Carries openkal-macos 0.9.1, whose release builds no longer fault before main (a returned register was declared an input), and openkal-windows 0.7.4, whose environment values keep their backslashes so commands run through the command interpreter work. Consumers reach this release through openkal-llvm-runtime = "0.9.5".
0.13.3
A detached thread ends and the program goes on. musl's __unmapself moved to a 256-byte stack shared by every exiting thread before the calls that end a detached one, and this port's path for those calls needed far more: the overflow overwrote the context table on arm64 macOS, and every program whose detached thread ended stopped there. The port now releases the mapping from the stack the thread is on. examples/threads-detached runs on every CI row.
Carries openkal-windows 0.7.3, whose channel ends reach only the program they are placed in (0.7.2) and whose started programs receive their arguments and working directory as given (0.7.3). Consumers reach this release through openkal-llvm-runtime = "0.9.4".
0.13.2
Every std::thread on Windows ended in an access violation when it was joined.
musl declares pthread_t as a pointer for C and as unsigned long for C++. On
LLP64 Windows a long is thirty-two bits, so C++ code above this library — libc++'s
std::thread among it — kept half of the thread's address, and pthread_join
read through the truncated value.
- C++
pthread_tis nowunsigned _Addr: unchanged (long) on x86_64, aarch64 and
riscv64,long longonx86_64-windows. Recorded inmusl/PATCHES.mdas the
fifth patched line. examples/threads-cxxasserts the width at compile time and joins a thread on
every CI row.
openkal-llvm-runtime pins this package exactly, so C++ programs reach the fix through openkal-llvm-runtime 0.9.3 (mcpplibs/openkal-llvm-runtime#18).
0.13.1
examples/subprocess now starts a #! script and asserts it runs, and the
README paragraph that blamed the emulator is corrected.
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 what the first
attempt did, answering EACCES and looking exactly like the defect being hunted.
Carries openkal-linux 0.12.0, which is where the fix is.
0.13.0
should have accompanied the feature.
kill(-n) fell back to the unit THIS program holds whenever n matched no
child. So every negative identifier named the caller's own unit: an enquiry
about a unit that does not exist was answered yes, and a signal aimed at one
ended the caller and everything it led. Measured — a program that formed a
unit and called kill(-99999, SIGKILL) ended on signal 9.
The fallback was written to serve fork(); setpgid(0, 0); exec, and it cannot
serve it: there the unit belongs to the copy and its handle is the copy's, and
openkal handles do not cross that boundary. openkal 0.12 records the whole of it
in clause 11 entry 9. kill(-n) now reaches this program's own unit only when
the caller NAMED it, and answers ESRCH otherwise.
⭐ The coverage gap is the real finding. The whole of setpgid/kill(-n)
shipped in 0.12.0 with its only witness in a consumer's test suite.
examples/subprocess now checks it here, including a control that proves the
observation can fail.
Two README rows described a world two releases old. addchdir_np was
answered in 0.12.0 and the table still said it was not; units arrived in 0.12.0
and the table still said "there are no process groups". Both corrected, and the
permission rows now state what to write INSTEAD of what cannot be written.
0.12.0
A started program runs where the caller is, a unit can be formed and terminated,
and three calls that answered success while doing nothing now do something.
Measured against a consumer's own test suite on both architectures: 96 pass
/ 12 fail became 108 pass / 0 fail.
What openkal 0.11 made possible
chdir reaches a started program. A spawn now carries a second directory,
so okm_cwd_dir — which is what chdir has always moved, this library having
nowhere else to move — is passed as the directory the program runs in. That
closes the fork route too: a copy that chdir'd has its own, and the execve it
then performs reaches the same line.
posix_spawn_file_actions_addchdir_np is answered, having been refused along
with everything this file could not express.
setpgid(0, 0) forms a real unit, and kill(-n) reaches it — including
programs the caller never held a handle to, which is what a unit is for.
unit is kept past the wait: a shell exits at once and the work it backgrounded is
exactly what a timeout has to reach.
Three answers that were true and did nothing
Each is the failure shape okm_opt.h names — nothing here reports success
having done nothing — and each was found by a consumer rather than by a probe
here.
A polled read returned one byte. openkal has no readiness enquiry, so poll
performs a bounded transfer and keeps what it produced; read then returned only
that. ⭐ Every byte was delivered and in order, which is why it survived: only a
caller that looks at the boundaries sees anything wrong, and then it sees a
lot — two. arrives as t and wo.. This is what openkal-linux#13's "only
output one byte" meant.
A condition variable with two waiters never woke. FUTEX_REQUEUE answered
ENOSYS and musl's unlock_requeue does not check, so nothing woke anybody. ⭐
It takes two waiters — this repository's own probe contends a mutex eighty
thousand times across four contexts and passed throughout.
The execve waiter held every stream open. A replacement leaves one image;
this composition leaves two, and the second still held the write end of a pipe it
had just given the started program. A pipe reports end of input when the last
writer lets go. spawn_bound was added
because a signal reached the middle image; this is the middle image holding a
resource.
Still absent, and said plainly
fork(); setpgid(0, 0); exec… cannot be closed: the unit that start forms
belongs to the program the copy starts, whose identity the original never
learns. posix_spawn with POSIX_SPAWN_SETPGROUP is the form that works, and it
is what the consumer moved to. A named group is still refused rather than quietly
turned into a different one.
0.11.0
Takes up openkal 0.10's five declarations, and fixes three defects that a
consumer's own test suite found — two of which needed no specification change at
all and had survived every probe in this repository.
Answers that are now true
fcntl(F_SETLK/F_SETLKW)takes a real lock throughkal_fs_lock. It used to
return success and take none.execvecomposed from a spawn now useskal_process_spawn_bound, so
terminating the caller reaches the program rather than the waiter standing
between them.sysconf(_SC_NPROCESSORS_ONLN)andstd::thread::hardware_concurrencyreport
the machine's count instead of 1.statvfs/std::filesystem::spaceanswer fromkal_fs_capacity.utimensaton a directory goes throughkal_fs_set_modified_at.getpidanswers per context, and a copy carries the identity it was given.
Three defects a consumer found, and this repository did not
A polled read returned one byte, so a polled stream arrived one byte at a
time. openkal has no readiness enquiry, so poll performs a bounded transfer
and keeps what it produced — one byte, deliberately. read then returned only
that byte, and a caller that polls goes straight back to poll. ⭐ Every byte
was delivered and in order, which is why it survived: only a caller that looks
at the boundaries sees anything wrong, and then it sees a lot — a reader
scanning each arrival for a word finds none, because two. arrives as t and
wo.. This is what openkal-linux#13's "only output one byte" meant, and it
was not truncation.
A condition variable with two waiters stopped for ever. FUTEX_REQUEUE
answered ENOSYS — and musl's unlock_requeue releases the barrier and then
makes that request without checking, so when it fails nothing wakes anybody. ⭐
It takes two waiters: the request is made only when a second context is queued
behind the one being released, and one waiter is what almost every program has.
A consumer's test passes on a host in 0.06s and did not finish here in 300.
Waking is a correct substitute for moving, because musl's waiter is always
inside while (a_cas(l, 0, 2)).
fcntl truncated a pointer on LLP64. musl reads its variable argument as
unsigned long, which holds a pointer everywhere musl was written for and
thirty-two bits on one target this port builds for. Unreachable until this
release, because every command answered before either took an integer or never
followed the pointer.
Still absent, and deliberately so
Two things a consumer needs cannot be said through openkal 0.10 and are not
faked here: the working directory a started program runs in (base names
the program, not where it runs), and terminating what a started program
itself started (there are no process groups). Both are refused honestly —
setpgid across processes reports EPERM, not success — and both are proposed
for 0.11 rather than simulated.
0.10.0 — the exec search, and the family of answers that were not true
openkal-linux#13, fifth round.
A consumer measured that execvp could not get past the first PATH entry that
missed — one libc path accounting for nine of their nineteen failures, plus
four sections silently skipped because bwrap, installed at /usr/bin/bwrap,
was reported as not installed. That is fixed, and reading it out found six more
of the same family.
⭐⭐ What they have in common
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 new, and is the probe that asks
the other question.
⚠️ ⚠️ The heaviest one was not in the report
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 now report ENOSYS.
⭐ That 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 — asked for as mcpplibs/openkal#22.
Answered
execvereturns when the program cannot be started, soexecvpcan search
a PATH.posix_spawnreportsENOENT/EACCESinstead of success.posix_spawnpsearches PATH at all — musl's source is replaced, the tenth.getppidno longer hands back -38 as an identifier (the defectgetpgrp
had one release earlier, three lines away, and that was not looked for).- A copy made by
forknames itself instead of reporting its parent. setpgid(0,0)→0andsetsid()→EPERM, the failure POSIX writes down.
Every daemonising library handlesEPERM; none handlesENOSYS.sigaltstackrefuses instead of reporting an installation it did not perform.sysconf(_SC_OPEN_MAX)answers the bound this library sets, not0.- A directory's modification time can be set where the implementation can
open a directory (Linux, macOS; not Windows). Reading it was never broken — the
consumer reported it as a read failure because libc++ gives both overloads of
last_write_timeone name.
⚠️ Not answered, and 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. Use posix_spawn, system or popen where you need
to stop what you started. Asked for as mcpplibs/openkal#23.
README.md and musl/PATCHES.md claimed a caller cannot distinguish
execve-as-spawn+wait from a real replacement. That claim was false, and is what
kept anyone from looking. Both now list the three known differences.
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 fails eleven of its observations on 0.9.0 and none 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.
0.9.0
A build can state which version of this library it holds.
Two rounds of mcpplibs/openkal-linux#13 were answered against the wrong version, and the software gave no way to settle the question. The only version-shaped thing a program could read was uname's release field, which was the string literal "0.5.0" through every release after 0.5.0. That is not a missing answer but a wrong one — a consumer who checked it was misled rather than left uncertain.
$ OPENKAL_MUSL_TRACE=enosys ./your-program
openkal-musl 0.9.0
openkal-musl: no operation for system call 266
The version line is printed before the program runs and whether or not anything is missing, once per process. That last part is the substance: a run in which nothing was refused printed nothing at all, so "the version is right and nothing is absent", "the variable did not take effect" and "this is not the binary I think it is" were one reading.
uname's release field carries the same string. It is read from mcpp.toml by build.mcpp rather than written out a second time, so it cannot drift; a manifest that cannot be read yields unknown, which is a true statement.
gethostname and getdomainname are musl's only consumers of uname and both read nodename — but a program above that compares the field against a fixed string will see it change. Recorded in README.md's divergence table.
Nothing changes for a program that does not ask. With the variable unset the library writes zero bytes to the error stream, and that is asserted in continuous integration on every row, precisely so this cannot become noise in an ordinary run.