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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,16 @@ conditional on the target.

```toml
[dependencies]
openkal = "0.9.0"
openkal = "0.10.0"

[target.'cfg(os = "linux")'.dependencies]
openkal-linux = "0.7.1"
openkal-linux = "0.9.0"

[target.'cfg(os = "macos")'.dependencies]
openkal-macos = "0.6.1"
openkal-macos = "0.7.0"

[target.'cfg(windows)'.dependencies]
openkal-windows = "0.4.0"
openkal-windows = "0.5.0"
```

The program imports the interface and names no implementation.
Expand Down
73 changes: 71 additions & 2 deletions SPEC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# openkal Specification, version 0.9
# openkal Specification, version 0.10

## 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.9 specifies the core and optional interfaces. The reserved row is not
Version 0.10 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,3 +1033,72 @@ 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.
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
property of the format rather than of the environment, so `kal_fs_props`
answers it and a caller asks before it calls.

⭐ **And this one is the opposite of entry 6, which is why the two are next
to each other.** Permission was declined because the environments do not
agree that an identity exists. Every environment this specification targets
locks a byte range, and spells it almost identically. What was missing was a
word, not a capability.

⚠️ **What its absence cost, and it was not a refusal.** A C library above
this interface answers `fcntl(F_SETLK)`. With nothing here to answer it
with, one returned success and took no lock — **two programs held one
exclusive lock and neither could find out**. Measured against a host. This
entry exists so that "a specification with no operation for X" is not
mistaken for "consumers of it will simply not do X".

⭐ **Release upon the holder's end is required rather than observed**, and
that requirement is the whole reason the operation cannot be composed above
the line: a caller can build exclusion out of `KAL_OPEN_EXCLUSIVE` and a
name, and nothing then releases that name when its holder dies.
11. **A started program that does not outlive its caller.** ⚠️ **Settled in
0.10.** `kal_process_spawn_bound`.

Clause 7.1 declines to replace a running image, and that stands. The
consequence, which this entry did not previously record, is that a C library
asked for `execve` composes it — and the composition leaves **three** images
where a system with the operation has two: the caller, a copy that waits,
and the program.

⚠️ **A signal reaches the middle one.** `kal_process_terminate` upon the
identifier the caller holds terminates the waiter; measured with a host as
control, the caller is told the program died on the signal it sent while the
program runs to completion, unsupervised. The termination operation was not
at fault — it was asked to terminate one started program and did. What was
missing was a way to *say* the thing `execve` means.
12. **How many contexts run at once.** ⚠️ **Settled in 0.10.**
`kal_task_parallelism`. `KAL_TASK_PROP_PARALLEL` says *whether* and not *how
many*, and a C library above had nowhere else to look: `hardware_concurrency`
answered 1 with no error, so a program sizing a pool of workers got one
worker and no way to know. ⭐ Zero means "cannot say" and is distinct from
one, because an environment with one processor and an environment that will
not answer call for different behaviour.
13. **The time of a name, and the size of a volume.** ⚠️ **Settled in 0.10.**
`kal_fs_set_modified_at` and `kal_fs_capacity`. The first exists because
`kal_fs_set_modified` is stated on an open *file* while a directory is
opened as a `kal_dir` — so this interface had no route to a directory's time
at all, and a consumer that stamps a lock directory drove one implementation
to open the directory for *reading* and set the time on that, outside
anything stated here. A divergence caused by a missing declaration is a
defect of the specification and is recorded as one.

The second, `kal_fs_capacity`, exists for the reason that keeps it in this
interface rather than in `openkal.space`: how much room a volume has is a
property of the *names* a caller can already reach, not of the memory a
program runs in. A C library above answers `statvfs`, and with nothing here
to answer it with it reported a fixed number — ⚠️ **which is worse than
refusing, because a program that checks for room before writing was told
there was room.**

⭐ It answers bytes and not blocks, and that is the whole of the design
decision. Every environment this specification targets states a block count
and a block size, in units of its own choosing, and every one of them
differs; a caller wanting bytes multiplies two numbers whose meaning it must
first look up. Bytes are what the caller is deciding about, so bytes are
what this returns and the multiplication happens once, in the
implementation, where the units are known.
8 changes: 7 additions & 1 deletion SURFACE.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# The C surface of openkal 0.9, one name per line.
# The C surface of openkal 0.10, one name per line.
#
# This file is normative and is the single source consulted by clause 9. A
# conforming implementation exports the names of the interfaces it provides
Expand Down Expand Up @@ -50,6 +50,7 @@ kal_time_wall
kal_random_fill
kal_random_props
# openkal.fs
kal_fs_capacity
kal_fs_close_dir
kal_fs_close_file
kal_fs_file_info
Expand All @@ -58,6 +59,7 @@ kal_fs_link_create
kal_fs_link_read
kal_fs_list_begin
kal_fs_list_next
kal_fs_lock
kal_fs_max_name
kal_fs_mkdir
kal_fs_open
Expand All @@ -69,20 +71,24 @@ kal_fs_remove
kal_fs_rename
kal_fs_seek
kal_fs_set_modified
kal_fs_set_modified_at
kal_fs_stream
kal_fs_truncate
kal_fs_unlock
# openkal.process
kal_process_channel
kal_process_channel_close
kal_process_close
kal_process_props
kal_process_spawn
kal_process_spawn_bound
kal_process_spawn_with
kal_process_terminate
kal_process_wait
# openkal.task
kal_task_current
kal_task_join
kal_task_parallelism
kal_task_props
kal_task_start
kal_task_wait
Expand Down
4 changes: 2 additions & 2 deletions conformance/mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-conformance"
version = "0.7.0"
version = "0.8.0"
description = "The behavioural half of clause 9: a suite an implementation of openkal runs against itself, selectable to the interfaces it provides."
license = "Apache-2.0"
authors = ["mcpplibs"]
Expand All @@ -18,7 +18,7 @@ repo = "https://github.com/mcpplibs/openkal"
# What it depends upon is therefore openkal and the language. The formatting in
# okc.report is the price of that, and it is sixty lines.
[dependencies]
openkal = "0.9.0"
openkal = "0.10.0"

# The implementation under examination is not named here.
#
Expand Down
6 changes: 6 additions & 0 deletions conformance/src/declarations.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void okc_declarations_c(void)
(void)sizeof(&kal_exec_publish);
(void)sizeof(&kal_exit);
(void)sizeof(&kal_free);
(void)sizeof(&kal_fs_capacity);
(void)sizeof(&kal_fs_close_dir);
(void)sizeof(&kal_fs_close_file);
(void)sizeof(&kal_fs_file_info);
Expand All @@ -54,6 +55,7 @@ void okc_declarations_c(void)
(void)sizeof(&kal_fs_link_read);
(void)sizeof(&kal_fs_list_begin);
(void)sizeof(&kal_fs_list_next);
(void)sizeof(&kal_fs_lock);
(void)sizeof(&kal_fs_max_name);
(void)sizeof(&kal_fs_mkdir);
(void)sizeof(&kal_fs_open);
Expand All @@ -65,8 +67,10 @@ void okc_declarations_c(void)
(void)sizeof(&kal_fs_rename);
(void)sizeof(&kal_fs_seek);
(void)sizeof(&kal_fs_set_modified);
(void)sizeof(&kal_fs_set_modified_at);
(void)sizeof(&kal_fs_stream);
(void)sizeof(&kal_fs_truncate);
(void)sizeof(&kal_fs_unlock);
(void)sizeof(&kal_interfaces);
(void)sizeof(&kal_memory_granularity);
(void)sizeof(&kal_net_accept);
Expand All @@ -85,6 +89,7 @@ void okc_declarations_c(void)
(void)sizeof(&kal_process_close);
(void)sizeof(&kal_process_props);
(void)sizeof(&kal_process_spawn);
(void)sizeof(&kal_process_spawn_bound);
(void)sizeof(&kal_process_spawn_with);
(void)sizeof(&kal_process_terminate);
(void)sizeof(&kal_process_wait);
Expand All @@ -101,6 +106,7 @@ void okc_declarations_c(void)
(void)sizeof(&kal_stream_write);
(void)sizeof(&kal_task_current);
(void)sizeof(&kal_task_join);
(void)sizeof(&kal_task_parallelism);
(void)sizeof(&kal_task_props);
(void)sizeof(&kal_task_start);
(void)sizeof(&kal_task_wait);
Expand Down
2 changes: 1 addition & 1 deletion conformance/src/report.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ int failed_count() { return g_failed; }
int unobserved_count() { return g_unobserved; }

void write_inventory() {
line("openkal conformance suite, version 0.9.0");
line("openkal conformance suite, version 0.10.0");
line("");
line("interface provision examined select with");
for (const auto& row : inventory) {
Expand Down
108 changes: 107 additions & 1 deletion conformance/src/sections/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,13 +478,119 @@ void run() {
}
}

// --- exclusion upon a range of a file, version 0.10 ---------------------
//
// ⭐⭐ THE OBSERVATION THAT TELLS THE TWO FORMS OF THIS APART NEEDS NO SECOND
// PROGRAM, AND THAT IS WHY IT IS WRITTEN THIS WAY.
//
// openkal states the holder as the open FILE. One environment's oldest form
// holds it by the PROCESS and releases every lock upon a node as soon as the
// program closes any descriptor for it --- so a library that opened one file
// twice destroyed its own lock. An implementation built on that form passes
// "a lock can be taken" and "it can be released" and fails only here: a
// SECOND OPEN FILE of one name, in this program, must be refused.
if (performs(kind::behaviour)) {
if ((kal_fs_props(here()) & kal::fs::locks.bits) != 0) {
put_file(kName, "xxxx");
kal_file a{}, b{};
const int oa = kal::fs::open_file(here(), kName, length(kName),
kal::fs::open::read | kal::fs::open::write, &a);
const int taken = oa == kal_ok
? kal::fs::lock_range(a, 0, 0, kal::fs::lock::exclusive) : oa;
observe(kind::behaviour, taken == kal_ok,
"an exclusive lock upon a whole file is taken");

const int ob = kal::fs::open_file(here(), kName, length(kName),
kal::fs::open::read | kal::fs::open::write, &b);
const int second = ob == kal_ok
? kal::fs::lock_range(b, 0, 0, kal::fs::lock::exclusive) : ob;
observe(kind::behaviour, second == kal_err_again,
"and a second open file of the same name is refused, not granted");

const int freed = taken == kal_ok ? kal_fs_unlock(a, 0, 0) : kal_err_invalid;
observe(kind::behaviour, freed == kal_ok, "the lock is released");
const int again = ob == kal_ok
? kal::fs::lock_range(b, 0, 0, kal::fs::lock::exclusive) : ob;
observe(kind::behaviour, again == kal_ok,
"and once released, another open file may take it");
if (again == kal_ok) kal_fs_unlock(b, 0, 0);

// Neither kind and both kinds are the same mistake: a caller that
// asked for neither did not say what it wanted.
observe(kind::behaviour,
oa != kal_ok || kal_fs_lock(a, 0, 0, 0) == kal_err_invalid,
"asking for a lock that is neither shared nor exclusive is refused");

if (ob == kal_ok) kal_fs_close_file(b);
if (oa == kal_ok) kal_fs_close_file(a);
kal_fs_remove(here(), kName, length(kName));
} else {
unobserved(kind::behaviour,
"an exclusive lock upon a whole file is taken",
"the implementation does not claim prop_locks for this volume");
}

// --- how much the volume holds --------------------------------------
if ((kal_fs_props(here()) & kal::fs::capacity.bits) != 0) {
kal_u64 total = 0, available = 0;
const int e = kal_fs_capacity(here(), &total, &available);
observe(kind::behaviour, e == kal_ok && total > 0,
"the volume reports how much it holds");
observe(kind::behaviour, e == kal_ok && available <= total,
"and what is available is no more than that");
// Either pointer may be null, for a caller that wants one of the two.
kal_u64 one = 0;
observe(kind::behaviour,
kal_fs_capacity(here(), nullptr, &one) == kal_ok,
"and a caller may ask for one of the two");
} else {
unobserved(kind::behaviour, "the volume reports how much it holds",
"the implementation does not claim prop_capacity for this volume");
}

// --- the modification time of a NAME, including a directory ----------
//
// ⭐ THE DIRECTORY IS THE POINT. `kal_fs_set_modified' takes a `kal_file'
// and a directory is a `kal_dir', so before this declaration there was no
// route to a directory's time at all --- and an implementation reached one
// anyway, outside anything this specification stated.
if ((kal_fs_props(here()) & kal::fs::modified_time.bits) != 0) {
kal_fs_mkdir(here(), kDir, length(kDir));
const kal_u64 chosen = 1600000000ull * 1000000000ull;
const int e = kal_fs_set_modified_at(here(), kDir, length(kDir), chosen);
kal_node_info after = fresh();
const int read_back =
kal_fs_info(here(), kDir, length(kDir), 0, kal::fs::field::all, &after);
observe(kind::behaviour,
e == kal_ok && read_back == kal_ok
&& after.modified_ns / 1000000000u == chosen / 1000000000u,
"the time a DIRECTORY reports as its last modification is set by name");
kal_fs_remove(here(), kDir, length(kDir));

put_file(kName, "x");
const int fe = kal_fs_set_modified_at(here(), kName, length(kName), chosen);
kal_node_info fa = fresh();
const int fr = kal_fs_info(here(), kName, length(kName), 0, kal::fs::field::all, &fa);
observe(kind::behaviour,
fe == kal_ok && fr == kal_ok
&& fa.modified_ns / 1000000000u == chosen / 1000000000u,
"and so is a file's, by the same operation");
kal_fs_remove(here(), kName, length(kName));
} else {
unobserved(kind::behaviour,
"the time a DIRECTORY reports as its last modification is set by name",
"the implementation does not claim prop_modified_time");
}
}

if (performs(kind::abi)) {
observe(kind::abi, sizeof(kal_dir) == sizeof(kal_uintptr)
&& sizeof(kal_file) == sizeof(kal_uintptr),
"a directory and a file handle each occupy one machine word");
const kal_uintptr assigned = (kal::fs::case_sensitive | kal::fs::links
| kal::fs::modified_time | kal::fs::atomic_rename
| kal::fs::make_links).bits;
| kal::fs::make_links
| kal::fs::locks | kal::fs::capacity).bits;
observe(kind::abi, (kal_fs_props(here()) & ~assigned) == 0,
"the capability word contains no position the specification has not assigned");

Expand Down
22 changes: 21 additions & 1 deletion conformance/src/sections/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,31 @@ void run() {
"the stream selection occupies three machine words");
const kal_uintptr assigned = (kal::process::terminate | kal::process::stream_passing
| kal::process::exit_status | kal::process::channel
| kal::process::grant_dir).bits;
| kal::process::grant_dir
| kal::process::bound_lifetime).bits;
observe(kind::abi, sizeof(kal_preopen) == 3 * sizeof(kal_uintptr),
"a directory grant occupies three machine words");
observe(kind::abi, (kal_process_props() & ~assigned) == 0,
"the capability word contains no position the specification has not assigned");

// ⚠️ AN OPERATION THAT IS NOT CLAIMED SHALL REFUSE RATHER THAN PERFORM
// SOMETHING ELSE. A caller that asks for a bound lifetime asked for it;
// a program started WITHOUT the binding is not the program it asked to
// start, and an implementation that quietly starts one anyway is the
// failure the operation exists to remove.
if ((kal_process_props() & kal::process::bound_lifetime.bits) == 0) {
kal_process p{};
const char* argv[1] = { "x" };
const kal_uintptr lens[1] = { 1 };
const int e = kal_process_spawn_bound(kal::fs::working(), "x", 1, argv, lens, 1,
nullptr, nullptr, 0, nullptr, &p);
observe(kind::behaviour, e == kal_err_not_supported,
"a lifetime this implementation cannot bind is refused, not ignored");
} else {
unobserved(kind::behaviour,
"a lifetime this implementation cannot bind is refused, not ignored",
"the implementation claims prop_bound_lifetime");
}
}

if (performs(kind::stability)) {
Expand Down
11 changes: 11 additions & 0 deletions conformance/src/sections/task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,17 @@ void run() {
| kal::task::thread_local_storage).bits;
observe(kind::abi, (kal_task_props() & ~assigned) == 0,
"the capability word contains no position the specification has not assigned");
// ⭐ ZERO IS `CANNOT SAY' AND IS DISTINCT FROM ONE, which is the whole
// reason this enquiry exists: before it, a C library above answered 1
// with no error and a program sizing a pool of workers got one worker.
// An implementation that will not say must say so, and not say "one".
const kal_uintptr n = kal_task_parallelism();
observe(kind::behaviour,
(kal_task_props() & kal::task::parallel.bits) == 0 ? n == 1 : true,
"an implementation whose contexts do not run at once reports one");
observe(kind::behaviour, kal_task_parallelism() == n,
"how many contexts can run at once is the same answer each time");

observe(kind::abi, kal_task_current() == kal_task_current(),
"the identity of the calling context is stable within it");

Expand Down
Loading
Loading