Skip to content

0.7.0 — adopt openkal 0.9, and make the aarch64 leg work for the first time - #18

Merged
Sunrisepeak merged 4 commits into
mainfrom
abi/one-idea-one-spelling
Aug 28, 2026
Merged

0.7.0 — adopt openkal 0.9, and make the aarch64 leg work for the first time#18
Sunrisepeak merged 4 commits into
mainfrom
abi/one-idea-one-spelling

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

Adopts openkal 0.9, and fixes two defects that meant the aarch64 leg of this
implementation had never worked
. Both were found by running it; neither could
have been found by building it.

⚠️⚠️ aarch64 used x86_64's numbers

The open flags. O_DIRECTORY, O_NOFOLLOW and O_DIRECT have one set of
values on x86_64 and another in the kernel's architecture-independent header,
which is what aarch64 uses:

x86_64 aarch64
O_DIRECTORY 0200000 040000
O_NOFOLLOW 0400000 0100000
O_DIRECT 040000 0200000

So on aarch64 this asked for O_DIRECT where it meant O_DIRECTORY, and the
kernel refuses O_DIRECT on a directory. Every directory failed to open
including the two preopens supplied at inception, which are every directory a
program above this can reach. Measured: kal_fs_preopen_count answered two and
both entries reported kal_err_permission with a handle of zero. A C library
above then had nothing to resolve a name against, so every open answered
ENOENT and getcwd answered / — which reads as a program started somewhere
odd rather than as an implementation that opened nothing.

The stat record. The kernel's architecture-independent struct stat places
the mode immediately after the device and inode; the layout here was neither that
nor x86_64's. The mode was read from offset 60, where the kernel writes a group,
and the size from 32, where it writes a device number. The same program on both:

x86_64    file: kind=1 size=10 writable=1   link: kind=3
aarch64   file: kind=4 size=0  writable=0   link: kind=4

Every node on aarch64 was "some other kind of thing", of length zero and not
writable — so a C library reported that a file it had just written ten bytes to
was not a regular file, and every operation that decides upon a kind decided
wrongly. ⭐ The offsets are now asserted: a field read from the wrong offset
is a wrong answer and not a failure, so the build could not report it; now it can.

⚠️ Nothing in this ecosystem could have caught either. The conformance suite runs
on the machine that builds it, and every hosted machine in CI is x86_64 or an
arm64 Mac — which uses openkal-macos and a different record again. The aarch64
leg was built and never run.

openkal 0.9

kal_fs_props takes the directory and consults the format the volume is. It took
no argument and claimed KAL_FS_PROP_CASE_SENSITIVE unconditionally — false on
any machine with a FAT volume mounted, which this implementation makes reachable
because it offers the whole filesystem as a preopen — and it never claimed
KAL_FS_PROP_LINKS while every operation here met them.

⚠️ And asking now resolves a link, because opening always did. kal_fs_info
passed AT_SYMLINK_NOFOLLOW always while kal_fs_open did not set O_NOFOLLOW
— the constant was declared in sys.h and used nowhere. So in one program,
opening a name reached a file and asking about it reported a link.

Also: transfers return one signed word; values are copied into the caller's
buffer; kal_node_info carries its own size, what was filled, and the device and
inode as an opaque identity; kal_fs_link_create/kal_fs_link_read over
symlinkat/readlinkat; kal_memory_granularity from the auxiliary vector;
kal_fs_max_name; typed stream handles; kal_version and kal_interfaces.

Conformance: 168 observations hold, up from 143. Surface: 95/95 on both
x86_64 and aarch64.

speak-agent and others added 4 commits August 29, 2026 00:46
…d per implementation

Every report becomes an operation, so an implementation reports what is true of
the resource the caller named rather than one word for the whole machine. The
change that has teeth is `kal_fs_props`:

  * it took no argument and claimed KAL_FS_PROP_CASE_SENSITIVE unconditionally.
    This implementation offers the whole filesystem as a preopen, so a FAT
    volume mounted anywhere on the machine is reachable through it, and the
    claim is false there. It now consults the format the resource is on;
  * it never claimed KAL_FS_PROP_LINKS while every operation here met them.

⚠️ AND ASKING NOW RESOLVES A LINK, BECAUSE OPENING ALWAYS DID.

`kal_fs_info` passed AT_SYMLINK_NOFOLLOW always while `kal_fs_open` did not set
O_NOFOLLOW --- the constant was declared in sys.h and used nowhere. So in one
program, opening a name reached a file and asking about it reported a link. A C
library above this reported `is_regular_file` as false for a name whose bytes it
could read, and one such node made a whole tree uncopyable.

The rest follows the specification:

  * transfers return one signed word;
  * `kal_env_*` and `kal_fs_preopen` and `kal_fs_list_next` copy into the
    caller's buffer and report the length the value has;
  * `kal_node_info` carries its own size, reports what was filled, and carries
    the device and inode as an opaque identity a caller may compare and not
    read --- which is what stops two different files reading as one;
  * `kal_fs_link_create` and `kal_fs_link_read` over symlinkat and readlinkat,
    with availability answered by the enquiry before they are called;
  * `kal_memory_granularity` from the auxiliary vector rather than a constant;
  * `kal_fs_max_name`, because a bound a caller cannot learn produces a failure
    it cannot attribute --- a longer name was refused as `kal_err_invalid`,
    which is also the answer for a name that ascends;
  * `kal_fs_stream` and `kal_spawn_streams` carry their type;
  * `kal_version` and `kal_interfaces`, answered with constants.

168 conformance observations hold, up from 143.
… a kind

Two defects, both from one habit: a constant that differs between architectures
written once, as though it did not.

⚠️⚠️ THE OPEN FLAGS. `O_DIRECTORY`, `O_NOFOLLOW` and `O_DIRECT` have one set of
values on x86_64 and another in the kernel's architecture-independent header,
which is what aarch64 uses:

                     x86_64      aarch64
    O_DIRECTORY      0200000     040000
    O_NOFOLLOW       0400000     0100000
    O_DIRECT         040000      0200000

So on aarch64 this asked for O_DIRECT where it meant O_DIRECTORY, and the kernel
refuses O_DIRECT on a directory. EVERY DIRECTORY FAILED TO OPEN --- including the
two preopens supplied at inception, which are every directory a program above
this can reach. Measured: `kal_fs_preopen_count' answered two and both entries
reported `kal_err_permission' with a handle of zero. A C library above then had
nothing to resolve a name against, so every `open' answered ENOENT and `getcwd'
answered "/", which reads as a program started somewhere odd rather than as an
implementation that opened nothing.

⚠️⚠️ THE STAT RECORD. The kernel's architecture-independent `struct stat' places
the mode immediately after the device and inode; the layout here was neither
that nor x86_64's. The mode was read from offset 60, where the kernel writes a
group, and the size from 32, where it writes a device number. Measured, the same
program on both:

    x86_64    file: kind=1 size=10 writable=1   link: kind=3
    aarch64   file: kind=4 size=0  writable=0   link: kind=4

Every node on aarch64 was "some other kind of thing", of length zero and not
writable --- so a C library reported that a file it had just written ten bytes to
was not a regular file, and every operation that decides upon a kind decided
wrongly.

⭐ THE OFFSETS ARE NOW ASSERTED. A field read from the wrong offset is a wrong
answer and not a failure, so the build could not report it; now it can.

⚠️ AND NOTHING IN THIS ECOSYSTEM COULD HAVE CAUGHT EITHER. The conformance suite
runs on the machine that builds it, and every hosted machine in this ecosystem's
continuous integration is x86_64 or an arm64 Mac --- which uses openkal-macos and
a different record again. The aarch64 leg of this implementation was built and
never run. Both were found by running it: under qemu-aarch64 the port's own
POSIX probe reported seven failures where x86_64 reported none, and after these
two it reports one, which is the emulator's own handling of a program that
starts another.
⚠️ THE TESTS WERE NOT UPDATED AND CI IS WHERE THAT SHOWED. Seven of them use the
surface directly and every one of them failed to compile against 0.9 --- which is
the arrangement working: a package that changes an interface and does not change
what examines it has changed nothing that anybody checked.

One of them then failed at RUN rather than at compile, and the reason is worth
recording: the enquiry that reports a preopen's name now copies it into the
caller's buffer, so a call site that passed a null buffer and then read the name
compiled and dereferenced zero. The end of an enumeration is likewise the
iterator becoming zero rather than a pointer becoming null, because there is no
pointer any more.

⚠️⚠️ AND THE README TOLD A READER TO ASK FOR A VERSION FOUR MINOR RELEASES OLD.
Every README here opens by showing what a program writes in its manifest, which
is the first thing a reader copies and the last thing anyone edits. The
specification's own said `openkal = "0.5.1"` while the package was at 0.9.0.

⭐ The staleness is not the finding. That it was INVISIBLE is: the surface is
checked against SURFACE.txt, the declarations against both forms, the behaviour
against the conformance suite --- and the one line a reader actually types was
checked by nobody. `tools/check-readme-versions.sh` in the specification's
repository now checks it, and reports `?` rather than passing where it cannot
look, so its "no" and its "did not run" do not read the same.

7 passed, 0 failed.
…es not

glibc calls every .init_array entry with (argc, argv, envp). musl calls them
with NO ARGUMENTS. src/env.cpp declared a constructor taking three and recorded
what arrived, which under musl is whatever the argument registers happened to
hold -- so every program above this implementation and a musl C library was told
its argument count was a text address, and the first enquiry after the count
dereferenced a small integer as a pointer.

Found by running this package's tests for aarch64, where it reads as an
architecture defect. It is not, and the control that separates the two is
x86_64-linux-musl:

    target                argc         argv        envp
    x86_64-linux-gnu      1            <stack>     <stack>
    x86_64-linux-musl     0x4004c2     1           <stack>
    aarch64-linux-musl    0x405ee4     1           <stack>

Both musl rows are shifted by one and the glibc row is not, so the axis is the C
library and not the machine. Running only aarch64 would have attributed it to
the machine, and the fix would have been wrong.

The arguments are now checked rather than believed -- argv[argc] must be the
terminator, argc must be a count -- and where they do not hold the vectors are
recovered from `environ' by walking back over argv's terminator to the slot that
holds the count, which must equal the number of entries actually found. Where
even that does not hold, nothing is recorded and the program is told it has no
arguments, which is an answer rather than a fault.

`environ' is a WEAK reference and the independence check now says so as a rule
rather than as an exception: this one name is permitted only when its type
letter is weak. It is admissible where `puts' is not because a call into the
runtime a program supplied can re-enter this implementation without bound and a
pointer executes nothing, and because weak means a program with no C library
still links. A strong reference to the same name would make one required
silently, and the check now fails on it -- measured, both ways.

Verified: 7 tests pass on x86_64-linux-gnu, x86_64-linux-musl and
aarch64-linux-musl.
@Sunrisepeak
Sunrisepeak merged commit 844c2c5 into main Aug 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants