From f2eeefa953db5dbedfcd9e779ae58a7a717f3281 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 6 Nov 2026 05:18:50 +0900 Subject: [PATCH 1/4] feat: observe declared units through native identity --- src/process.cpp | 91 +++++++++++++++++++++++++++++++++++++++++++- src/unit_reference.h | 29 ++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/unit_reference.h diff --git a/src/process.cpp b/src/process.cpp index ebe9761..11b7e08 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -1607,7 +1607,7 @@ int kal_process_end_as_requested(void) { } kal_uintptr kal_process_props(void) { - return KAL_PROCESS_PROP_TERMINATE | KAL_PROCESS_PROP_STREAM_PASSING + return KAL_PROCESS_PROP_JOB_OBSERVE | KAL_PROCESS_PROP_TERMINATE | KAL_PROCESS_PROP_STREAM_PASSING | KAL_PROCESS_PROP_EXIT_STATUS | KAL_PROCESS_PROP_CHANNEL | KAL_PROCESS_PROP_GRANT_DIR | KAL_PROCESS_PROP_BOUND_LIFETIME @@ -1953,3 +1953,92 @@ int kal_process_job_limit(kal_job j, kal_uintptr kind, kal_u64 value) { } } + +// Observable units (openkal 0.24). The unit stays the kernel's own group; +// identity validation does not create a registry or infer quiet from a leader. +#include "unit_reference.h" +namespace { +struct unit_identity { + kal_u64 group = 0, birth = 0, previous = 0, previous_birth = 0; + char boot[64]{}, space[64]{}; +}; +bool unit_birth(okl_long pid, kal_u64& value) { okl_u64 ticks = 0; if (!started_ticks(pid,ticks)) return false; value = ticks; return true; } +bool unit_origin(char* boot, char* space) { + if (self_number() != okl::sys(okl::nr_getpid)) return false; + const auto fd = okl::sys(okl::nr_openat, okl::at_fdcwd, + reinterpret_cast("/proc/sys/kernel/random/boot_id"), okl::o_rdonly | okl::o_cloexec, 0); + if (okl::failed(fd)) return false; + auto n = okl::sys(okl::nr_read, fd, reinterpret_cast(boot), 63); + okl::sys(okl::nr_close, fd); + if (n <= 0 || n >= 63) return false; + while (n > 0 && (boot[n - 1] == '\n' || boot[n - 1] == '\r')) --n; + boot[n] = 0; + n = okl::sys(okl::nr_readlinkat, okl::at_fdcwd, + reinterpret_cast("/proc/self/ns/pid"), reinterpret_cast(space), 63); + if (n <= 0 || n >= 63) return false; + space[n] = 0; return true; +} +bool unit_parse(const char* p, kal_uintptr n, unit_identity& u) { + if (!p || n == 0 || n > 256) return false; + unit_reference::reader r{p,n}; char kind[8]{}; + return r.token(kind, sizeof kind) && unit_reference::same(kind,"L1") + && r.number(u.group) && u.group > 0 && u.group <= 0x7fffffffu + && r.number(u.birth) && u.birth != 0 + && r.number(u.previous) && u.previous > 0 && u.previous <= 0x7fffffffu + && r.number(u.previous_birth) && u.previous_birth != 0 + && r.token(u.boot,sizeof u.boot) && r.token(u.space,sizeof u.space) && r.at == n; +} +bool unit_same_origin(const unit_identity& u) { + char boot[64]{}, space[64]{}; + return unit_origin(boot,space) && unit_reference::same(u.boot,boot) && unit_reference::same(u.space,space); +} +} +extern "C" { +int kal_process_job_create(kal_job* out, char* reference, kal_uintptr capacity, kal_uintptr* used) { + if (!out || !reference || !used) return kal_err_invalid; + if (capacity < 256) return kal_err_no_space; + unit_identity u; + u.group = static_cast(okl::sys(okl::nr_getpid)); + u.previous = static_cast(okl::sys(okl::nr_getpgid,0)); + if (u.previous == u.group || !unit_origin(u.boot,u.space) + || !unit_birth(0,u.birth) || !unit_birth(static_cast(u.previous),u.previous_birth) + || !u.birth || !u.previous_birth) return kal_err_not_supported; + unit_reference::writer w; w.text("L1;"); w.number(u.group); w.next(); w.number(u.birth); w.next(); + w.number(u.previous); w.next(); w.number(u.previous_birth); w.next(); w.text(u.boot); w.next(); w.text(u.space); + if (!w.valid) return kal_err_no_space; + kal_job job{}; const int rc = kal_process_job_enter(&job); + if (rc != kal_ok) return rc; + *out = job; return unit_reference::copy(w,reference,capacity,used); +} +int kal_process_job_open(const char* reference, kal_uintptr length, kal_job* out) { + if (!out) return kal_err_invalid; + unit_identity u; if (!unit_parse(reference,length,u)) return kal_err_invalid; + if (!unit_same_origin(u)) return kal_err_not_supported; + out->h = static_cast(u.group); return kal_ok; +} +int kal_process_job_observe(kal_job job, const char* reference, kal_uintptr length, kal_u32* state) { + if (!state) return kal_err_invalid; *state = KAL_JOB_UNKNOWN; + unit_identity u; if (!unit_parse(reference,length,u) || job.h != u.group) return kal_err_invalid; + if (!unit_same_origin(u)) return kal_ok; + const auto status = okl::sys(okl::nr_kill,-static_cast(u.group),0); + if (status == -3 /* ESRCH */) { *state = KAL_JOB_EMPTY; return kal_ok; } + if (okl::failed(status)) return kal_ok; + okl_u64 birth = 0; + if (started_ticks(static_cast(u.group),birth) && birth != u.birth) return kal_ok; + *state = KAL_JOB_OCCUPIED; return kal_ok; +} +int kal_process_job_retire(kal_job job, const char* reference, kal_uintptr length, kal_u32* state) { + if (!state) return kal_err_invalid; *state = KAL_JOB_UNKNOWN; + unit_identity u; if (!unit_parse(reference,length,u) || job.h != u.group) return kal_err_invalid; + okl_u64 birth = 0; + if (static_cast(okl::sys(okl::nr_getpid)) != u.group || !unit_same_origin(u) + || !started_ticks(0,birth) || birth != u.birth) return kal_err_permission; + const auto current = okl::sys(okl::nr_getpgid,0); + if (current == static_cast(u.group)) { + if (!started_ticks(static_cast(u.previous),birth) || birth != u.previous_birth) return kal_ok; + const auto rc = okl::sys(okl::nr_setpgid,0,static_cast(u.previous)); + if (okl::failed(rc)) return kal_ok; + } else if (current != static_cast(u.previous)) return kal_ok; + return kal_process_job_observe(job,reference,length,state); +} +} diff --git a/src/unit_reference.h b/src/unit_reference.h new file mode 100644 index 0000000..b6fadac --- /dev/null +++ b/src/unit_reference.h @@ -0,0 +1,29 @@ +// Counted, opaque unit references. This codec stores no process registry. +#pragma once +#include +namespace unit_reference { +struct writer { + char bytes[256]{}; kal_uintptr size = 0; bool valid = true; + void text(const char* p) { for (; *p; ++p) { if (size == sizeof bytes) { valid = false; return; } bytes[size++] = *p; } } + void number(kal_u64 n) { char b[24]; unsigned used = 0; do { b[used++] = char('0' + n % 10); n /= 10; } while (n); while (used) { char c[2]{b[--used],0}; text(c); } } + void next() { text(";"); } +}; +struct reader { + const char* bytes; kal_uintptr size, at = 0; + bool token(char* p, kal_uintptr capacity) { + kal_uintptr n = 0; + while (at < size && bytes[at] != ';') { if (n + 1 >= capacity || bytes[at] < 33 || bytes[at] > 126) return false; p[n++] = bytes[at++]; } + p[n] = 0; if (at < size) ++at; return n != 0; + } + bool number(kal_u64& n) { + n = 0; const auto begin = at; + while (at < size && bytes[at] != ';') { const auto c = bytes[at++]; if (c < '0' || c > '9' || n > (~kal_u64{0} - kal_u64(c - '0')) / 10) return false; n = n * 10 + kal_u64(c - '0'); } + if (at == begin) return false; if (at < size) ++at; return true; + } +}; +inline bool same(const char* a, const char* b) { for (; *a && *a == *b; ++a, ++b) {} return *a == *b; } +inline int copy(const writer& w, char* out, kal_uintptr capacity, kal_uintptr* used) { + if (!w.valid || capacity < w.size) return kal_err_no_space; + for (kal_uintptr i = 0; i < w.size; ++i) out[i] = w.bytes[i]; *used = w.size; return kal_ok; +} +} From 5d2217424c25f072eb7bb8a7f6c2ea95b4bfa0df Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 6 Nov 2026 06:10:47 +0900 Subject: [PATCH 2/4] fix: retire units against a verified live continuation --- src/process.cpp | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/process.cpp b/src/process.cpp index 11b7e08..c051568 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -1959,7 +1959,7 @@ int kal_process_job_limit(kal_job j, kal_uintptr kind, kal_u64 value) { #include "unit_reference.h" namespace { struct unit_identity { - kal_u64 group = 0, birth = 0, previous = 0, previous_birth = 0; + kal_u64 group = 0, birth = 0; char boot[64]{}, space[64]{}; }; bool unit_birth(okl_long pid, kal_u64& value) { okl_u64 ticks = 0; if (!started_ticks(pid,ticks)) return false; value = ticks; return true; } @@ -1981,11 +1981,9 @@ bool unit_origin(char* boot, char* space) { bool unit_parse(const char* p, kal_uintptr n, unit_identity& u) { if (!p || n == 0 || n > 256) return false; unit_reference::reader r{p,n}; char kind[8]{}; - return r.token(kind, sizeof kind) && unit_reference::same(kind,"L1") + return r.token(kind, sizeof kind) && unit_reference::same(kind,"L2") && r.number(u.group) && u.group > 0 && u.group <= 0x7fffffffu && r.number(u.birth) && u.birth != 0 - && r.number(u.previous) && u.previous > 0 && u.previous <= 0x7fffffffu - && r.number(u.previous_birth) && u.previous_birth != 0 && r.token(u.boot,sizeof u.boot) && r.token(u.space,sizeof u.space) && r.at == n; } bool unit_same_origin(const unit_identity& u) { @@ -1999,12 +1997,9 @@ int kal_process_job_create(kal_job* out, char* reference, kal_uintptr capacity, if (capacity < 256) return kal_err_no_space; unit_identity u; u.group = static_cast(okl::sys(okl::nr_getpid)); - u.previous = static_cast(okl::sys(okl::nr_getpgid,0)); - if (u.previous == u.group || !unit_origin(u.boot,u.space) - || !unit_birth(0,u.birth) || !unit_birth(static_cast(u.previous),u.previous_birth) - || !u.birth || !u.previous_birth) return kal_err_not_supported; - unit_reference::writer w; w.text("L1;"); w.number(u.group); w.next(); w.number(u.birth); w.next(); - w.number(u.previous); w.next(); w.number(u.previous_birth); w.next(); w.text(u.boot); w.next(); w.text(u.space); + if (!unit_origin(u.boot,u.space)||!unit_birth(0,u.birth)||!u.birth) return kal_err_not_supported; + unit_reference::writer w; w.text("L2;"); w.number(u.group); w.next(); w.number(u.birth); w.next(); + w.text(u.boot); w.next(); w.text(u.space); if (!w.valid) return kal_err_no_space; kal_job job{}; const int rc = kal_process_job_enter(&job); if (rc != kal_ok) return rc; @@ -2027,18 +2022,28 @@ int kal_process_job_observe(kal_job job, const char* reference, kal_uintptr leng if (started_ticks(static_cast(u.group),birth) && birth != u.birth) return kal_ok; *state = KAL_JOB_OCCUPIED; return kal_ok; } -int kal_process_job_retire(kal_job job, const char* reference, kal_uintptr length, kal_u32* state) { +int kal_process_job_retire(kal_job job, const char* reference, kal_uintptr length, + const kal_program_identity* continuation, kal_u32* state) { if (!state) return kal_err_invalid; *state = KAL_JOB_UNKNOWN; unit_identity u; if (!unit_parse(reference,length,u) || job.h != u.group) return kal_err_invalid; okl_u64 birth = 0; if (static_cast(okl::sys(okl::nr_getpid)) != u.group || !unit_same_origin(u) || !started_ticks(0,birth) || birth != u.birth) return kal_err_permission; + if (!continuation || !continuation->id || continuation->id > 0x7fffffffu || !continuation->started_ns) + return kal_ok; + const auto anchor = static_cast(continuation->id); + if (!started_ticks(anchor,birth) || ticks_to_ns(birth) != continuation->started_ns + || stat_state(anchor) == 'Z' || stat_state(anchor) == 'X') return kal_ok; + const auto destination = okl::sys(okl::nr_getpgid,anchor); + if (destination <= 0 || destination == static_cast(u.group)) return kal_ok; const auto current = okl::sys(okl::nr_getpgid,0); if (current == static_cast(u.group)) { - if (!started_ticks(static_cast(u.previous),birth) || birth != u.previous_birth) return kal_ok; - const auto rc = okl::sys(okl::nr_setpgid,0,static_cast(u.previous)); - if (okl::failed(rc)) return kal_ok; - } else if (current != static_cast(u.previous)) return kal_ok; + if (okl::failed(okl::sys(okl::nr_setpgid,0,destination))) return kal_ok; + } else if (current != destination) return kal_ok; + if (!started_ticks(anchor,birth) || ticks_to_ns(birth) != continuation->started_ns + || stat_state(anchor) == 'Z' || stat_state(anchor) == 'X' + || okl::sys(okl::nr_getpgid,anchor) != destination + || okl::sys(okl::nr_getpgid,0) != destination) return kal_ok; return kal_process_job_observe(job,reference,length,state); } } From 0e9f3ccd7a1f473a2aeb399f727efec7196bae46 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 6 Nov 2026 07:41:04 +0900 Subject: [PATCH 3/4] chore: release observable process units with OpenKal 0.24 --- .github/workflows/ci.yml | 4 ++-- README.md | 12 ++++++++++-- mcpp.toml | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c76109c..31e356a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,8 +36,8 @@ env: # fails with exit 2. This is the CI pin -- what this repository is # tested against -- and not a floor: nothing here records a minimum # engine for consumers. - MCPP_VERSION: 2026.9.11.3 - XLINGS_VERSION: v2026.8.17.2 + MCPP_VERSION: 2026.11.5.1 + XLINGS_VERSION: v2026.10.26.1 XLINGS_NON_INTERACTIVE: '1' jobs: diff --git a/README.md b/README.md index 455ad4c..dbedd71 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,20 @@ for Linux, written on the kernel's own system-call interface. ```toml [dependencies] -openkal = "0.23.0" +openkal = "0.24.0" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.24.2" +openkal-linux = "0.25.0" ``` +## Observable process units + +The process interface provides the OpenKal 0.24 declared-unit operations. +References bind the original process-group identity to its birth, boot and +PID namespace. Retirement checks the live continuation before and after +membership migration; ambiguity reports `unknown`. No process registry is +installed. + ## Why it does not use a C library Version 0.4 of this implementation was written upon the C library of the host, diff --git a/mcpp.toml b/mcpp.toml index e0cdeff..dffa255 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.24.2" +version = "0.25.0" description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one." license = "Apache-2.0" @@ -67,7 +67,7 @@ provides-interfaces = [ ] [dependencies] -openkal = "0.23.0" +openkal = "0.24.0" # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package From b429bac4368d1a3960efd6d9dd93c02f229fbeda Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Fri, 6 Nov 2026 07:48:50 +0900 Subject: [PATCH 4/4] fix: keep standalone TLS independent under PIC --- src/task.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/task.cpp b/src/task.cpp index 6de61f8..d6b1f2e 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -213,7 +213,15 @@ struct self_bounds { int state; // 0 not yet asked, 1 answered }; -thread_local self_bounds g_self = { nullptr, 0, 0 }; +// A standalone implementation establishes the program's one native TLS block. +// PIC must not add a call into a C library's dynamic TLS resolver beneath it. +// The hosted arrangement retains the compiler's dynamic-loading convention. +#ifdef OKL_STANDALONE +#define OKL_TLS_MODEL __attribute__((tls_model("initial-exec"))) +#else +#define OKL_TLS_MODEL +#endif +thread_local OKL_TLS_MODEL self_bounds g_self = { nullptr, 0, 0 }; #ifdef OKL_STANDALONE @@ -243,7 +251,7 @@ okl_uptr pages_per_chunk() { // apart from "not mapped" because the two lead to different reports: a range // derived from a refusal is a range derived from nothing, and the caller is // owed the refusal instead. -thread_local okl_long g_refusal = 0; +thread_local OKL_TLS_MODEL okl_long g_refusal = 0; // Whether every page in the range is mapped. False with `mapped' unset means // at least one is not; false with `g_refusal' set means the environment would @@ -620,7 +628,7 @@ kal_uintptr kal_task_current(void) { // The identity is the kernel's, read once per context. It is unique among // contexts running at the same moment and may be reused after one ends, // which is what the specification says of it. - static thread_local int cached = 0; + static thread_local OKL_TLS_MODEL int cached = 0; if (cached == 0) cached = static_cast(okl::sys(okl::nr_gettid)); return static_cast(cached); }