Compose sockets, datagrams, readiness and fork above openkal 0.8 - #16
Merged
Conversation
The specification grew five interfaces in 0.8 and this port used none of them. A program above this library reached `socket' and was told ENOSYS because musl's network sources compile and issue system calls the dispatcher had no case for; `poll' and `select' the same; `fork' the same. Reported as mcpplibs/openkal-linux#13. The gap was here. port/src/okm_net.c the socket family upon openkal.net, the datagram family upon openkal.datagram. BSD makes a socket first and decides what it is afterwards, and openkal has no object between nothing and a connection --- so a descriptor made by `socket' holds three numbers and the openkal operation happens at the call that says what it is for. port/src/okm_poll.c readiness upon openkal.timeout. openkal has no operation reporting whether a transfer would proceed (clause 6.3 records why), so `poll' attempts the transfer under a bound and KEEPS what it produced --- one byte, one connection, one message --- for the operation that follows. port/src/okm_fork.c duplicating the calling image upon openkal.space. space.h describes this composition and says in terms that it belongs above the line: `setjmp' before the call, `longjmp' in the copy. An earlier reading of the report concluded that `fork' had been declined deliberately. Half of that is right: clause 7.1 declines to duplicate an address space AND ITS EXECUTION STATE. `openkal.space' supplies the first half by itself, and what was missing was never an atom. `posix_spawn' was likewise reported as missing and is not: okm_spawn.c has replaced musl's since this port was written, and `system' and `popen' work through it. What was missing there was a criterion. Every reference to the four interfaces is weak and exactly one name per interface is tested, because clause 3 requires an implementation to provide an interface in whole or not at all. A strong reference would turn "this backend has no network" into "no program above this library links", which this port has already been bitten by twice. Refused rather than approximated: permission bits, because `kal_node_info' carries one boolean and not a mode word; symbolic links, because SURFACE.txt has no operation that makes one; out-of-band data; readiness sets. `O_NONBLOCK' is now refused where `openkal.timeout' is absent, where it used to be accepted and carried no further. Also scopes musl's internal `hidden', `weak' and `weak_alias' to the overlay that defines them, so a program above this library may use the names (openkal-musl#13). `restrict' stays unconditional in C++: musl's PUBLIC headers write it, which is a property of those headers. Four probes, each written against POSIX and naming no openkal symbol, and each stating on its command line what it expects the backend beneath to provide --- so that a refusal is asserted as a refusal rather than merely not asked about.
The step asserted the rule for one name. There are eleven now: the four interfaces okm_net.c, okm_poll.c and okm_fork.c reach are optional in exactly the sense `openkal.random' is, and openkal-opensbi and openkal-uefi decline all four. A strong reference to any of them would make a bare-metal program that never opens a socket fail to link, which is the defect this step already exists because of.⚠️ ⚠️ AND THE FIRST FORM OF THE CHECK MEASURED THE WRONG OBJECTS, which is worth recording because it reported two real-looking failures. A search of everything under `target/' called `kal_net_accept' and `kal_datagram_recv_from' undefined STRONG references --- because the DEPENDENCY's objects are there too, under `obj/mcpplibs_*', and openkal-linux's `kal_timeout_accept' refers to its own `kal_net_accept' strongly. That is correct for an implementation and says nothing about this port. ⇒ `-maxdepth 1', and one fingerprint directory asserted before anything is read. The step now examines 1341 of this package's own objects and reports the number, so a search that found nothing cannot pass.
⚠️ ⚠️ `tools/run-probe.sh' DID NOT PARSE, ON ALL FOUR ROWS AT ONCE. name="${2:?the program's name}" Bash parses the text of `${2:?...}' with quoting active, so the apostrophe opens a single quote that never closes --- and the report arrives thirty lines later as tools/run-probe.sh: line 53: syntax error near unexpected token `(' naming a line that is correct.⚠️ NOTHING LOCAL HAD RUN THE SCRIPT. The four probes were exercised by running their binaries directly, so the runner --- which is what CI actually invokes --- was written and never executed. `bash -n' reproduces it in a second, which is the whole cost of the check that was not made. And `examples/identifiers' now prints the same last line every other probe prints, so one runner reads all four rather than three plus a bespoke step. Its criterion is unchanged and is still the compile.
⚠️ ⚠️ okm_context.c keeps this library's per-context state --- its error value, its locale, its thread record --- in a table keyed on `kal_task_current()'. The specification says that identity is "unique among contexts running at the same moment and may be reused after one ends". It says NOTHING about a copy of the address space, and the two implementations answer differently: openkal-linux caches `gettid' in a thread-local, so the COPY of the cache answers the parent's value and the lookup works openkal-macos asks `thread_selfid' every time, so the started context is a NEW thread of a NEW process and answers a value the table has never seen ⇒ The second is not a defect. It is the honest answer to the question the interface asks, and the assumption that a copy keeps its identity was this port's. ⭐ MEASURED, AND THE PORT'S OWN DIAGNOSTIC NAMED IT. On the macOS row the copy stopped with openkal-musl: this execution context has no per-context state --- the implementation's kal_task_current did not answer the same value here as it did when the context started which is the message `__okm_get_tp' has carried since it was written, for exactly this condition. Without it the report would have been a copy that ended on a signal, four layers from the cause --- and the probe reported only "it did not report the status it was written to report", which names a fault and not a place. The probe now prints the raw status when it is wrong, for the same reason. The started context therefore rebinds its slot before anything reads per-context state, from two globals written in the original --- a local written between `setjmp' and `longjmp' is indeterminate in the resumed context.
Found reviewing the composition rather than by a failure, which is why it is worth stating: the table of started programs lives in this port's own memory, so a copy of the address space inherits it --- and POSIX is explicit that a duplicate has no children. Left in place the entries are worse than useless. A copy that called `wait' would be told about a program it did not start and cannot wait for, and `system' inside a copy --- which waits for the child it just started --- could be handed one of the original's instead. The handles are not released: they belong to the original, which is still holding them. Only the copy's view of them is cleared, in the started context, before anything reads the table.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported as
mcpplibs/openkal-linux#13andmcpplibs/openkal-musl#13: aprogram above this library was told
ENOSYSforsocket,bind,listen,accept,connect,poll,selectandfork, and could not declarehiddenas an ordinary identifier.
openkal 0.8 added the atoms for all of the first group. Nothing above them used
any of them: musl's network sources compile and issue system calls the
dispatcher had no case for. The gap was here, not in musl's sources and not in
the specification.
What is added
port/src/okm_net.copenkal.net, the datagram family uponopenkal.datagramport/src/okm_poll.copenkal.timeoutport/src/okm_fork.copenkal.spaceobject between "nothing" and "a connection". So a descriptor made by
socketholds the three numbers it was given, and the openkal operation happens at the
call that says what the socket is for —
bindrecords an endpoint,listenspends it,
connectspends its own. A caller cannot observe the deferral: everyerror a kernel reports at
bindthis port reports atlisten, one call laterand with the same value.
clause 6.3 records why.
polltherefore attempts the transfer under a bound andkeeps what it produced — one byte, one connection, one message — for the
operation that follows.
POLLINasserts that a read will not block, and a bytealready in hand is the strongest form that assertion can take.
Two corrections to the plan this implements
posix_spawnwas never missing. The plan said the exclusion ofmusl/src/process/posix_spawn.chad to be answered by a port implementation.port/src/okm_spawn.chas replaced that source since the port was written,and
systemandpopenwork through it. Measured:system("exit 5")returnsan exit status of 5,
popencarries a line back. What was missing was acriterion —
examples/subprocessis it.timeout_ns = 0does not mean "do not wait". It means no bound;timeout.hsays so andkal_task_waitestablished it. Passing a caller's zerostraight through turns the one call that must not wait into the one that never
returns — measured, as a hang, four lines into the network probe.
SYS_cloneis not the only numberforkarrives through. musl's_ForkissuesSYS_forkwhere the architecture has it, so a dispatcherimplementing only
SYS_cloneis reached on aarch64 and riscv64 and never onx86_64.
forkis composed here, and the specification asked for itspace.hstates in terms that a library above reachesforkby saving its ownexecution state before the call and restoring it in the started context, and
that this belongs above the line.
okm_setjmp.Salready carried theper-architecture half.
An earlier reading of the report concluded that
forkhad been declineddeliberately. Half of that is right: clause 7.1 declines to duplicate an address
space and its execution state.
openkal.spacesupplies the first half byitself, and what was missing was never an atom.
What is refused, and why each refusal is stated rather than approximated
kal_node_infocarries a booleanwritable, not a modeword. Mapping the owner-write bit onto it would make
chmod(0600)succeed andstatreport something else.SURFACE.txthas no operation that creates one. It doeshave
kal_node_linkandKAL_FS_PROP_LINKS, so an implementation can report alink and cannot make one; the asymmetry is the specification's and is recorded.
epollstays withheld).O_NONBLOCKwhereopenkal.timeoutis absent — it used to be accepted andcarried no further, which is the one shape this port exists to avoid.
Criteria
Four probes, each written against POSIX, naming no openkal symbol, and each
stating on its command line what it expects the backend beneath to provide — so
a refusal is asserted as a refusal rather than merely not asked about.
examples/netexamples/subprocess--no-forkrequires the refusalexamples/identifiershidden,weak,weak_aliasas the program's own names — a compile-time criterionexamples/posixAnd in continuous integration: eleven names asserted weak, with
kal_time_sleepas the strong control. A strong reference to any optionalinterface would make a bare-metal program that never opens a socket fail to
link — the defect that step already exists because of, arriving through a new
name.
check's fault: a search under
target/reaches the dependency's objects,where openkal-linux's
kal_timeout_acceptrefers to its ownkal_net_acceptstrongly. That is correct for an implementation and says nothing about this
port.
This is one change across seven repositories, and every branch carries the
same name. Each repository's continuous integration substitutes its siblings'
working trees taken from the branch of the name under test, so the graph only
holds together when they agree.
openkal-muslopenkal-macosopenkal-windowsopenkal.spacedeclined in termsopenkal-linuxopenkal-llvm-runtime__config_site's claims asserted by a program, and the C library repinnedopenkalsbaseThe specification does not change. No interface was added, none was altered,
and
SURFACE.txtis untouched — every capability below is composed from atomsopenkal 0.8 already had.