Releases: mcpplibs/openkal-musl
Release list
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.
0.8.0
truncate(2), and the backends that fixed the EAGAIN this port reported.
truncate (76) had no case. Only ftruncate was answered, and the two are not interchangeable to a caller holding a name and no open file — which is every caller of std::filesystem::resize_file, since libc++ expresses it as detail::truncate(p.c_str(), size). Composed as SYS_utimensat beside it already is.
Repins openkal-linux 0.7.1 and openkal-macos 0.6.1. Those correct kal_timeout_read and kal_timeout_write, which decoded a borrowed kal_stream handle with the decoder for an owned one and so waited upon the descriptor below the one they transferred upon. This port is where that was observed and is not where it was: libc++ opens a copy's source with O_NONBLOCK, do_read routes that through the bounded-transfer path, and copy_file_range is a read/write loop — so every byte of a file copy passed through the wrong wait, and EAGAIN is not in libc++'s fallback list.
A directory's modification time: reading it works and setting it is refused, and the two are now observed separately. libc++ gives both last_write_time overloads the same operation name, so a failure in either reads identically. The refusal is asserted and the value reported — it is EISDIR on Linux and macOS and EACCES on Windows, and belongs to the backend rather than to this port.
Reported in mcpplibs/openkal-linux#13. .agents/docs/2026-08-30-issue13-round-four-timeout-truncate-and-provenance.md records the round.
0.7.0
openkal-musl 0.7.0 — the reported crash, and what adopting openkal 0.9 exposed
The crash
rt_sigprocmask returned without writing the caller's old-mask buffer. A
program that read it acted on uninitialised stack. Three lines reproduce it and
the fix is one case in the dispatcher.
What links made possible, and what they exposed
symlinkat and readlinkat are answered through openkal 0.9's
kal_fs_link_create / kal_fs_link_read, and fstatat honours
AT_SYMLINK_NOFOLLOW — so stat and lstat are two questions again. One
symlink used to make a whole directory tree uncopyable through std::filesystem.
Then fs::remove_all on a directory holding a link returned ENOTEMPTY and left
the tree standing, while every operation it is built from behaved correctly.
O_NOFOLLOW on a link must report ELOOP; this port resolved (openkal offers no
opening that declines to) and answered ENOENT, and libc++ reads ENOENT as
"the entry is already gone". The enquiry that answers it is the one openkal 0.9
added.
openkal's granularity is not this library's page size
kal_memory_granularity() is the coarsest quantum a caller must respect, and an
implementation for a machine with no memory management unit answers one —
correctly. libc.page_size is a different thing wearing the same name. Given
one, the allocator asked for one-byte extents; over openkal-opensbi a program
printed three of its four lines and stopped. The answer is now taken as a floor.
The probe's assertion had said "a positive power of two", and one is both.
Also: node identity from kal_node_info rather than constants, the environment
copied out, getpgid/getpgrp/getsid, membarrier, and the child table
raised from 64 to 256.
openkal-musl 0.3.1
Supersedes 0.3.0, whose manifest named the implementation of openkal for each system
by a path. That is right in a working tree that has the repositories side by
side and wrong in a package: a path names a directory that exists where the
manifest was written and nowhere else, so a consumer resolving it from the index
was handed a dependency pointing into the registry's own store.
-openkal-linux = { path = "../openkal-linux", features = ["standalone"] }
+openkal-linux = { version = "0.5.1", features = ["standalone"] }The feature travels beside the version, because a program above this library
carries no other runtime and that is what the feature states.
Found by resolving the package the way a stranger would, from a store with every
local copy deleted. A working tree masks every mistake in a manifest's
dependencies, because the tree is what the manifest was written against.
The contents are otherwise those of 0.3.0; its release notes describe them.
openkal-musl 0.3.0
musl 1.2.5 redirected onto openkal, running
on Linux, macOS and Windows.
The package was openkal-libc up to 0.2.0 — a probe of 243 lines exporting 14
names, which was not a C library. The version line continues rather than
restarting: a tag is the one thing in the release chain that cannot be given a
second meaning.
The shape of the port
musl reaches its environment through one seam: seven inline functions in
arch/<arch>/syscall_arch.h, plus __get_tp in pthread_arch.h. Replacing that
one header is the whole of the redirection, and 1345 sources are compiled
unmodified.
musl/PATCHES.md lists the whole of what is not unmodified: four patched
lines and nine replaced sources, each with its reason.
| lines | |
|---|---|
| the system-call correspondence | 923 |
| descriptors and name resolution | 434 |
| startup and the thread pointer | 241 |
setjmp and its relatives |
223 |
| execution contexts and the suspension primitive | 202 |
| starting another program | 163 |
| where per-context state is kept | 128 |
| what two object formats do not provide | 105 |
| how a second name is made | 133 |
| the rest | 241 |
| total | 2793 |
Against 1345 musl sources compiled unmodified. Between the first system and the
third the port grew by 800 lines, and every one of those is about an object
format or a naming convention rather than about a kernel.
Three object formats
| ELF | the reference case |
| PE | measured, with both toolchains: a weak symbol is not a definition there. The 289 aliases are made strongly, and the 46 names musl provides as placeholders are not made at all — decided by the name of the placeholder's target, so no site of musl's is edited |
| Mach-O | the compiler refuses the construct: aliases are not supported on darwin. The assembler's directive makes the name instead, and the same placeholder list applies |
Four findings that only running produced
A C library cannot keep its per-context state in a thread_local variable. On
one toolchain such a variable is reached through a helper that allocates, and the
allocator is what the C library provides.
long double is not a property of the architecture alone. On Apple arm64 it
is 53 bits where musl's arch/aarch64/bits/float.h says 113 — measured on all
four combinations. printf("%.3f", 1.5) stopped in frexpl with the value read
as infinity. port/src/okm_float_assert.c now asserts the agreement.
A program above this library must not have a C++ runtime embedded into it. On
one system the build tool embeds one and runs its initialiser before the entry
point, when the C library has not started.
An object format's linker divides a section into atoms, and a section with no
content has no atom — the four names musl walks between went missing with it.
What runs above it
examples/posix |
32 observations, on all three systems |
examples/wordcount |
the same counts as the system's own wc |
mcpplibs/sbase |
all 97 suckless base utilities, sources unmodified, 50 comparisons |
Continuous integration: Linux (gcc, llvm), macOS (llvm), Windows (gcc, PE).
openkal-libc 0.2.0
0.2.0: the tests are given an implementation, and the example is asserted against wc