diff --git a/README.md b/README.md index de4178d..94e49c7 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ conditional on the target. ```toml [dependencies] -openkal = "0.10.0" +openkal = "0.12.0" [target.'cfg(os = "linux")'.dependencies] openkal-linux = "0.9.0" diff --git a/SPEC.md b/SPEC.md index 34bbd44..aca9af1 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,4 +1,4 @@ -# openkal Specification, version 0.11 +# openkal Specification, version 0.12 ## 1. Scope @@ -50,7 +50,7 @@ provides an interface in whole or not at all. | `openkal.timeout` | a bound upon operations that would otherwise wait | optional | ✓ | ✓ | ✓ | | `openkal.event` | readiness of a set of resources | reserved | | | | -Version 0.11 specifies the core and optional interfaces. The reserved row is not +Version 0.12 specifies the core and optional interfaces. The reserved row is not specified, and its name shall not be used for other purposes. The S, L and X columns state which boundaries an interface's declarations can @@ -1033,6 +1033,27 @@ The following are recorded so that they are not mistaken for oversights. general mechanism for passing a handle to a context in another space is not defined by this version. It is the question `openkal.space` reaches first and is not peculiar to it. + + ⭐ **What this costs, named in 0.12 because an implementation paid it.** A + unit is established by whoever starts a program, at the moment of starting it: + `kal_spawn.job` is where a caller says which unit a started program belongs + to, and the implementation performs the placement. It follows that a program + cannot name a unit that a *copy of itself* went on to form — the copy's handle + is the copy's, constructed by clause 6.7 from an index into the copy's own + table, and nothing above conveys it back. So the sequence a C library reaches + for when it composes job control — duplicate the image, have the copy form a + unit of its own, then signal that unit from the original — has no closure + here, and this is the reason rather than an omission. + + ⚠️ **The failure this produces is not a refusal, which is why it is recorded.** + An implementation that meets the dead end is invited to reach for the nearest + unit it *can* name, which is its own; openkal-musl 0.12.0 did exactly that, + and every negative identifier that matched no child named the caller's own + unit. A program asking after a unit that did not exist was told it did, and + one signalling it ended itself together with everything it led. The composition + that does work is the one this entry began with: start the program with + `kal_spawn.job`, where the unit and the caller that knows its name are on the + same side of the boundary. 10. **Exclusion upon a range of a file.** ⚠️ **Settled in 0.10.** `kal_fs_lock` and `kal_fs_unlock` are operations of `openkal.fs`, admitted on exactly the grounds entry 7 records for links: whether a *volume* can exclude is a diff --git a/conformance/src/macros_reach.cpp b/conformance/src/macros_reach.cpp new file mode 100644 index 0000000..73bb617 --- /dev/null +++ b/conformance/src/macros_reach.cpp @@ -0,0 +1,43 @@ +// ⭐⭐ THAT `openkal.macros' CAN BE IMPORTED AND ITS NAMES USED --- which is a +// different claim from the one CI already makes about it. +// +// ⚠️⚠️ THE MODULE WAS ADDED TO STOP A DEFECT CLASS AND HAD THE SAME DEFECT. A +// macro does not cross a module boundary, so every KAL_ macro is regenerated +// here under a `_M' spelling, and continuous integration regenerates the file +// and diffs it. That step asserts the file is UP TO DATE. It does not assert +// that anybody can use it, and until this translation unit was written nothing +// in this ecosystem had ever imported the module --- so "generated correctly" +// was checked on every commit and "reaches a consumer" was checked never. +// +// It was found the first time something tried: a conformance check in +// openkal-macos named `KAL_PROCESS_PROP_STOP_REQUESTED_M' unqualified, because +// the module's own comment says the C spelling "always exists here too", and +// the name is in fact `kal::macros::KAL_PROCESS_PROP_STOP_REQUESTED_M'. The +// qualification is right --- it is the house style every other module follows +// --- and what was missing was a consumer to state it. +// +// ⇒ This file is that consumer. It runs nothing: a `static_assert' upon a value +// fails the build both when the NAME does not resolve and when the VALUE is +// wrong, which are the two ways the generated file could betray a caller. + +import openkal.macros; + +namespace { + +// The values are those the headers define. Written out rather than compared +// against the macro, because in a module translation unit the macro is exactly +// what is not available --- which is the whole reason this module exists. +static_assert(kal::macros::KAL_PROCESS_PROP_TERMINATE_M == 1u); +static_assert(kal::macros::KAL_PROCESS_PROP_STREAM_PASSING_M == 2u); +static_assert(kal::macros::KAL_PROCESS_PROP_EXIT_STATUS_M == 4u); +static_assert(kal::macros::KAL_PROCESS_PROP_CHANNEL_M == 8u); +static_assert(kal::macros::KAL_PROCESS_PROP_GRANT_DIR_M == 16u); +static_assert(kal::macros::KAL_PROCESS_PROP_BOUND_LIFETIME_M == 32u); +static_assert(kal::macros::KAL_PROCESS_PROP_JOB_M == 64u); +static_assert(kal::macros::KAL_PROCESS_PROP_STOP_REQUESTED_M == 128u); + +// One from another interface, so that a generator which emitted only the last +// header it read would be caught. +static_assert(kal::macros::KAL_SPAWN_BOUND_LIFETIME_M == 1u); + +} // namespace diff --git a/include/openkal/fs.h b/include/openkal/fs.h index 760254b..1837fd4 100644 --- a/include/openkal/fs.h +++ b/include/openkal/fs.h @@ -198,7 +198,22 @@ int kal_fs_open_dir(struct kal_dir base, const char* name, kal_uintptr len, * behind if the program stops in between; exclusion tested before opening is * not exclusion; and appending performed by seeking is not appending when a * second writer exists. Clause 3.1 classifies each of those as a simulation, so - * the specification states the intent instead. */ + * the specification states the intent instead. + * + * ⭐⭐ AND THE WORD DOES NOT CARRY A PERMISSION, WHICH IS STATED HERE BECAUSE + * HERE IS WHERE IT IS LOOKED FOR. A caller creating a file and meaning "only I + * may read this" finds no flag for it, and clause 11 entry 6 gives the reason: a + * permission presupposes an identity, and the environments this specification + * targets do not agree that one exists. That entry also gives the three answers + * a program has instead, sorted by whom it is defending against --- another part + * of the same program, which the capability already excludes, since a handle not + * given cannot be reached; another user of the machine, which is the party that + * STARTED the program, through the preopens it supplies and withholds; a + * location the program does not trust, which is encryption. + * + * ⚠️ Read as a gap this reads as one. It is a position, and the position is + * WASI's: `fs_rights_base' there attaches to a handle and not to a file, and the + * analogue here is the word above. */ int kal_fs_open(struct kal_dir base, const char* name, kal_uintptr len, kal_uintptr flags, struct kal_file* out); @@ -252,6 +267,10 @@ int kal_fs_truncate(struct kal_file, kal_u64 size); int kal_fs_info(struct kal_dir base, const char* name, kal_uintptr len, kal_uintptr flags, kal_u32 wanted, struct kal_node_info* out); +/* Creating a directory, which takes no permission for the reason `kal_fs_open' + * above states in full and clause 11 entry 6 gives: a directory meant to be + * private is one whose PREOPEN was not handed to anybody else, and that is the + * decision of the party that starts a program rather than of the program. */ int kal_fs_mkdir (struct kal_dir base, const char* name, kal_uintptr len); int kal_fs_remove(struct kal_dir base, const char* name, kal_uintptr len); int kal_fs_rename(struct kal_dir from, const char* a, kal_uintptr alen, diff --git a/include/openkal/process.h b/include/openkal/process.h index f1e87f5..73024f1 100644 --- a/include/openkal/process.h +++ b/include/openkal/process.h @@ -166,7 +166,23 @@ struct kal_spawn { * wrapped. Terminating a unit whose leader is long gone can therefore reach a * different unit on such a system. This is what those systems do --- every * program that calls `killpg' lives with it --- and it is recorded rather than - * hidden behind an interface that reads as though it were not so. */ + * hidden behind an interface that reads as though it were not so. + * + * ⭐⭐ AND THIS IS WHERE A UNIT IS ESTABLISHED --- AT THE START, BY WHOEVER + * STARTS. A caller names the unit here and the implementation performs the + * placement, so the unit and the program that can name it are on the same + * side of the boundary. `kal_process_job_enter' is the other half and is not + * a way around this: it places THE CALLER, for the sake of the spawns the + * caller goes on to perform. + * + * ⚠️ IT FOLLOWS THAT A COPY'S UNIT CANNOT BE NAMED BY THE ORIGINAL, and a C + * library composing job control meets this rather than reading it. Duplicate + * the image, have the copy form a unit, then signal that unit from the + * original: the copy's handle is the copy's --- clause 6.7 builds it from an + * index into the copy's own table --- and clause 11 entry 9 conveys nothing + * back. The composition has no closure, and the entry records what an + * implementation did when it met the dead end rather than leaving the next + * one to repeat it. */ struct kal_job* job; /* The directories the started program receives, read back through diff --git a/mcpp.toml b/mcpp.toml index bf0596f..d1d37d9 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal" -version = "0.11.0" +version = "0.12.0" description = "openkal: a portable kernel ABI specification. This package carries the normative declarations; implementations are separate packages." license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/tools/check-surface.sh b/tools/check-surface.sh index 83a829d..e90cb74 100755 --- a/tools/check-surface.sh +++ b/tools/check-surface.sh @@ -48,7 +48,20 @@ fi status=0 while read -r name; do [ -n "$name" ] || continue - if ! printf '%s\n' "$spec" | grep -qx "$name"; then + # ⚠️⚠️ A HERE-STRING AND NOT A PIPE, AND THE DIFFERENCE IS A FALSE RED. + # + # `grep -q' exits at the FIRST match, which closes the pipe under a `printf' + # that may still be writing; the printf then dies of SIGPIPE, and `pipefail' + # at the top of this file makes the whole pipeline report failure. The `!' + # inverts that into "not in the specification" --- for a name that IS in it. + # + # ⚠️ It depends on whether the list fits in the pipe buffer before grep + # exits, so it fires occasionally and passes on a re-run, which is the worst + # shape a check can have: openkal-macos reported `kal_stdin' missing on one + # run and clean on the next with no change between them. The name is the + # twenty-seventh line of SURFACE.txt --- an early match is exactly the case + # that leaves the most left to write. + if ! grep -qxF -- "$name" <<< "$spec"; then echo "exported name is not in the specification: $name" >&2 status=1 fi @@ -74,7 +87,10 @@ if [ "$complete" -eq 1 ]; then local present=0 absent=0 missing='' while read -r name; do [ -n "$name" ] || continue - if printf '%s\n' "$found" | grep -qx "$name"; then present=$((present+1)) + # A here-string for the reason given above. Here the same SIGPIPE + # would UNDER-COUNT what is exported, so a complete interface would + # be reported as half of one. + if grep -qxF -- "$name" <<< "$found"; then present=$((present+1)) else absent=$((absent+1)); missing="$missing $name"; fi done <<< "$want" if [ "$present" -gt 0 ] && [ "$absent" -gt 0 ]; then