Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
25 changes: 23 additions & 2 deletions SPEC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# openkal Specification, version 0.11
# openkal Specification, version 0.12

## 1. Scope

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions conformance/src/macros_reach.cpp
Original file line number Diff line number Diff line change
@@ -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
21 changes: 20 additions & 1 deletion include/openkal/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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,
Expand Down
18 changes: 17 additions & 1 deletion include/openkal/process.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand Down
20 changes: 18 additions & 2 deletions tools/check-surface.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading