From c0575289814b4ceb2dd8eb5fec2ed87aa8e8dcdb Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 3 Nov 2026 02:49:49 +0900 Subject: [PATCH 1/8] 0.2.8 --- openkal 0.23's implementations: the directories a started program receives, with their rights, and confinement and a resource preference asked of the start; a stream that goes nowhere is the system's null device at its position; ok images over openkal-platform 0.23.0 Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/ci.yml | 16 ++++---- README.md | 25 +++++++++++- checks/hosted/mcpp.toml | 2 +- mcpp.toml | 12 +++--- src/process.cpp | 88 +++++++++++++++++++++++++++++++++++++--- src/process.cppm | 33 +++++++++++++++ tests/process_test.cpp | 40 ++++++++++++++++++ 7 files changed, 195 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fbdfd9..58b27a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,8 +15,8 @@ name: CI # # The library is built with the released mcpp, 2026.11.2.1. The ok images # (MCPP_PLATFORM) and checks/hosted (its manifest) select openkal-platform -# 0.21.0 and take their C library and C++ run-time from it (openkal 0.21's -# implementations among them; openkal-musl 0.26.0 embeds its allocator, which +# 0.23.0 and take their C library and C++ run-time from it (openkal 0.23's +# implementations among them; openkal-musl 0.27.0 embeds its allocator, which # needs mcpp 2026.11 or later). The library # itself needs 2026.10.27.1 or later, the first that selects a platform # (SPEC-013). A dispatch may name a branch of mcpp instead (`mcpp_ref'), which @@ -220,10 +220,10 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 120 env: - # openkal 0.21.0, which this release is written against, is the one - # openkal-platform 0.21.0's version group names; mcpp 2026.11.2.2 and + # openkal 0.23.0, which this release is written against, is the one + # openkal-platform 0.23.0's version group names; mcpp 2026.11.2.3 and # later pin it for their openkal rows. - MCPP_PLATFORM: openkal-platform@0.21.0 + MCPP_PLATFORM: openkal-platform@0.23.0 defaults: run: shell: bash @@ -270,10 +270,10 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 45 env: - # openkal 0.21.0, which this release is written against, is the one - # openkal-platform 0.21.0's version group names; mcpp 2026.11.2.2 and + # openkal 0.23.0, which this release is written against, is the one + # openkal-platform 0.23.0's version group names; mcpp 2026.11.2.3 and # later pin it for their openkal rows. - MCPP_PLATFORM: openkal-platform@0.21.0 + MCPP_PLATFORM: openkal-platform@0.23.0 strategy: fail-fast: false matrix: diff --git a/README.md b/README.md index 8d89724..8a9c24d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ with compile-time platform constants and a few platform modules. ```toml [dependencies] -os = { namespace = "mcpplibs", version = "0.2.6" } +os = { namespace = "mcpplibs", version = "0.2.8" } ``` ```cpp @@ -56,6 +56,29 @@ itself: the kernel's descriptors and dispositions, or on Windows the process's s handles. `checks/hosted` runs the tests under openkal-musl and openkal-llvm-runtime on every system in CI. +## A program's stream that goes nowhere + +`process::stdio::null` gives a started program the system's null device at that +position --- `/dev/null` on Linux and macOS, `NUL` on Windows --- through the start +itself (openkal 0.22, `KAL_SPAWN_NULL_IN`, `_OUT`, `_ERR`), so this program holds +no pipe and no thread for it (0.2.8). An implementation before openkal 0.22 is +given a file of the system's own where there is one, or a pipe this program +closes (input) or drains (output), as before. + +## The directories a started program receives + +`spawn_options::grants` names the directories a started program receives, each +under the name it knows it by, the first being the one it regards as the one it +was started in; each may be read-only, stated unchanging (the implementation may +keep what it read beneath it) and recorded (each name the program reaches, +and whether it was there, is appended to a file) --- openkal 0.23's +`kal_fs_dir_attenuate`, whose rights pass with the handle to every program the +started one starts. `confine` asks the system to hold the program to its +grants as well (Landlock on Linux, the sandbox on macOS), and `preference` +states what it spends resources for (balanced, performance, economy). Each is +refused with `errc::not_supported` where the system cannot give it, never +dropped (0.2.8). + ## Modules | Module | Contents | diff --git a/checks/hosted/mcpp.toml b/checks/hosted/mcpp.toml index 2852c1a..e62fe2a 100644 --- a/checks/hosted/mcpp.toml +++ b/checks/hosted/mcpp.toml @@ -13,7 +13,7 @@ version = "0.1.0" os = { path = "../.." } [target.'cfg(any(linux, macos, windows))'] -platform = "openkal-platform@0.21.0" +platform = "openkal-platform@0.22.0" # The tests are the package's own, each reached by an include from # tests/.cpp here (a test program is a file of the package that runs it). diff --git a/mcpp.toml b/mcpp.toml index 945a43d..a15db54 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "os" -version = "0.2.7" +version = "0.2.8" description = "mcpplibs.os: a C++ operating-system library over openkal --- processes, files, environment, terminals, directories and local IPC, with compile-time platform constants and a few platform modules." license = "Apache-2.0" authors = ["mcpplibs"] @@ -12,23 +12,23 @@ repo = "https://github.com/mcpplibs/os" # own C and C++ runtime. A program that wants the standalone arrangement, or # the image arrangement of `-openkal`, selects it in its own manifest. [dependencies] -openkal = "0.21.0" +openkal = "0.23.0" [target.linux.dependencies] -openkal-linux = "0.22.0" +openkal-linux = "0.24.0" [target.macos.dependencies] -openkal-macos = "0.19.0" +openkal-macos = "0.21.0" [target.windows.dependencies] -openkal-windows = "0.17.0" +openkal-windows = "0.19.0" # Emscripten (`wasm32-emscripten`): openkal written above the C library, with # twelve of openkal's fifteen interfaces. `openkal.process', `openkal.exec' and # `openkal.space' are absent, so a program that starts one fails at link # naming the symbol; `openkal.task' is present in a `-pthread' link only. [target.'cfg(os = "emscripten")'.dependencies] -openkal-emscripten = "0.10.0" +openkal-emscripten = "0.12.0" [targets.os] kind = "lib" diff --git a/src/process.cpp b/src/process.cpp index f4658c2..fb7f88a 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -47,9 +47,12 @@ kal_u64 nanoseconds(std::chrono::milliseconds t) { return static_cast(ns < 1 ? 1 : ns); } -// Where a program's stream goes when it is to go nowhere. A file of the -// system's own where there is one; otherwise a pipe whose far end this program -// closes (input) or drains (output). +// Where a program's stream goes when it is to go nowhere, for an +// implementation that does not give the system's null device at a position +// (before openkal 0.22): a file of the system's own where there is one; +// otherwise a pipe whose far end this program closes (input) or drains +// (output). From 0.22 the start is asked for the device itself +// (`KAL_SPAWN_NULL_*`, process::spawn). struct null_stream { kal_stream child{}; bool channel = false; // `child' is an end this program must release @@ -193,6 +196,11 @@ result process::spawn(const spawn_options& o) { auto s = std::make_unique(); std::vector release; // the program's ends, released after the start std::vector nulls; + // THE SYSTEM'S NULL DEVICE AT A POSITION (openkal 0.22): asked of the start + // where the implementation states it, so nothing here holds a pipe or a + // thread for a program whose stream goes nowhere. + const bool device = (kal_process_props() & kal::process::null_streams.bits) != 0; + kal_uintptr null_at = 0; kal_stream child_in{}, child_out{}, child_err{}; auto fail_streams = [&](const error& e) { for (auto st : release) kal_process_channel_close(st); @@ -206,6 +214,8 @@ result process::spawn(const spawn_options& o) { if (const int rc = kal_process_channel(&child_in, &s->input); rc != kal_ok) return fail_streams(from_kal(rc, "making the program's input")); s->input_open = true; release.push_back(child_in); + } else if (o.in == stdio::null && device) { + null_at |= kal::process::null_in_flag.bits; } else if (o.in == stdio::null) { auto n = open_null(true); if (!n) return fail_streams(n.error()); @@ -215,6 +225,8 @@ result process::spawn(const spawn_options& o) { if (const int rc = kal_process_channel(&s->output, &child_out); rc != kal_ok) return fail_streams(from_kal(rc, "making the program's output")); s->output_open = true; release.push_back(child_out); + } else if (o.out == stdio::null && device) { + null_at |= kal::process::null_out_flag.bits; } else if (o.out == stdio::null) { auto n = open_null(false); if (!n) return fail_streams(n.error()); @@ -224,11 +236,14 @@ result process::spawn(const spawn_options& o) { } if (o.err == stdio::output) { if (o.out == stdio::inherit) child_err = kal_stdout(); + else if (null_at & kal::process::null_out_flag.bits) null_at |= kal::process::null_err_flag.bits; else child_err = child_out; } else if (o.err == stdio::pipe) { if (const int rc = kal_process_channel(&s->error, &child_err); rc != kal_ok) return fail_streams(from_kal(rc, "making the program's error output")); s->error_open = true; release.push_back(child_err); + } else if (o.err == stdio::null && device) { + null_at |= kal::process::null_err_flag.bits; } else if (o.err == stdio::null) { auto n = open_null(false); if (!n) return fail_streams(n.error()); @@ -255,9 +270,72 @@ result process::spawn(const spawn_options& o) { if (o.one_string && (props & kal::process::one_string.bits) == 0) return fail_streams(error{ errc::not_supported, std::format("starting {}: a program here receives a vector, not one string", fs::generic_utf8(program)) }); - kal_spawn how{ as_dir(where->directory), work, nullptr, nullptr, 0, + // THE DIRECTORIES THE PROGRAM RECEIVES (0.2.8, openkal 0.23), each opened + // and given the rights asked for, and released once the program started. + struct granted { + std::vector dirs; + std::vector records; + std::vector preopens; + ~granted() { + for (auto d : dirs) kal_fs_close_dir(d); + for (auto f : records) kal_fs_close_file(f); + } + } g; + if (o.grants) { + if ((props & kal::process::grant_dir.bits) == 0) + return fail_streams(error{ errc::not_supported, + std::format("starting {}: this system grants a started program no directories", fs::generic_utf8(program)) }); + g.preopens.reserve(o.grants->size()); + for (const auto& gr : *o.grants) { + const auto at = fs::locate(fs::absolute(gr.path).lexically_normal()); + if (!at) return fail_streams(error{ errc::not_found, + std::format("granting {}: beneath no directory this program was given", fs::generic_utf8(gr.path)) }); + kal_dir d{}; + if (const int rc = kal_fs_open_dir(as_dir(at->directory), at->relative.data(), at->relative.size(), &d); rc != kal_ok) + return fail_streams(from_kal(rc, std::format("granting {}", fs::generic_utf8(gr.path)))); + g.dirs.push_back(d); + kal_dir_rights rights{}; + rights.self_size = sizeof rights; + if (gr.read_only) rights.flags |= kal::fs::dir_read_only.bits; + if (gr.unchanging) rights.flags |= kal::fs::dir_unchanging.bits; + if (!gr.record.empty()) { + const auto r = fs::locate(fs::absolute(gr.record).lexically_normal()); + if (!r) return fail_streams(error{ errc::not_found, + std::format("recording {}: beneath no directory this program was given", fs::generic_utf8(gr.record)) }); + kal_file rec{}; + const auto how_open = kal::fs::open::write | kal::fs::open::create | kal::fs::open::append; + if (const int rc = kal_fs_open(as_dir(r->directory), r->relative.data(), r->relative.size(), how_open.bits, &rec); rc != kal_ok) + return fail_streams(from_kal(rc, std::format("recording {}", fs::generic_utf8(gr.record)))); + g.records.push_back(rec); + rights.flags |= kal::fs::dir_recorded.bits; + rights.record = rec; + } + if (rights.flags != 0) { + if ((kal_fs_props(d) & kal::fs::attenuate.bits) == 0) + return fail_streams(error{ errc::not_supported, + std::format("granting {}: this system cannot hold a directory to rights", fs::generic_utf8(gr.path)) }); + kal_dir held{}; + if (const int rc = kal_fs_dir_attenuate(d, &rights, &held); rc != kal_ok) + return fail_streams(from_kal(rc, std::format("granting {}", fs::generic_utf8(gr.path)))); + g.dirs.push_back(held); + d = held; + } + g.preopens.push_back(kal_preopen{ d, gr.name.data(), gr.name.size() }); + } + } + if (o.confine && (props & kal::process::confine.bits) == 0) + return fail_streams(error{ errc::not_supported, + std::format("starting {}: this system cannot hold a program to its grants", fs::generic_utf8(program)) }); + const kal_uintptr preference = + o.preference == resources::balanced ? kal::process::balanced_flag.bits + : o.preference == resources::performance ? kal::process::performance_flag.bits + : o.preference == resources::economy ? kal::process::economy_flag.bits : kal_uintptr{ 0 }; + kal_spawn how{ as_dir(where->directory), work, nullptr, + o.grants ? g.preopens.data() : nullptr, o.grants ? g.preopens.size() : 0, (bind ? kal::process::bound_lifetime_flag.bits : kal_uintptr{ 0 }) - | (o.one_string ? kal::process::one_string_flag.bits : kal_uintptr{ 0 }) }; + | (o.one_string ? kal::process::one_string_flag.bits : kal_uintptr{ 0 }) + | (o.confine ? kal::process::confine_flag.bits : kal_uintptr{ 0 }) + | preference | null_at }; kal_job job{}; const bool unit = o.own_unit && (props & kal::process::job.bits) != 0; if (unit) how.job = &job; diff --git a/src/process.cppm b/src/process.cppm index 4af3723..8c5e639 100644 --- a/src/process.cppm +++ b/src/process.cppm @@ -21,6 +21,29 @@ export namespace mcpplibs::os::process { // which is the order a person reads a compiler's diagnostics in. enum class stdio { inherit, pipe, null, error, output }; +// A DIRECTORY A STARTED PROGRAM RECEIVES (openkal 0.23): the name it knows it +// by, the directory, and what it may do beneath it. A program that reaches +// files only through what it was given is held to these by openkal; one +// started with `confine' is held to them by the system as well. +struct grant { + // The name, normally the directory's absolute path, by which the program + // resolves an absolute name beneath it. + std::string name; + std::filesystem::path path; + // Nothing beneath is created, written, removed or renamed. + bool read_only = false; + // The caller states that nothing beneath changes while the program runs, + // so the implementation may keep what it read of it. + bool unchanging = false; + // Each name the program reaches beneath, and whether it was there, is + // appended to this file (openkal's record: `r', `w', `s', `a', `l' and the + // name, then `e' when the program ends); empty for none. + std::filesystem::path record; +}; + +// What a started program spends its machine's resources for (openkal 0.23). +enum class resources { unstated, balanced, performance, economy }; + struct spawn_options { // An absolute name, or a bare one looked for on PATH (`env::find_executable'). std::filesystem::path program; @@ -44,6 +67,16 @@ struct spawn_options { // the line as a command of its own grammar (0.2.0, openkal 0.17.1). // errc::not_supported where a program receives a vector. bool one_string = false; + // The directories the program receives, the first being the one it + // regards as the one it was started in; none given, those the + // implementation supplies by default. errc::not_supported where the + // implementation grants no directories, or a right asked for cannot be + // held (0.2.8, openkal 0.23). + std::optional> grants; + // The program is held to its grants by the system, not by openkal alone; + // errc::not_supported where the system cannot hold it. + bool confine = false; + resources preference = resources::unstated; }; // How a program ended: the status it returned, or, ended by its environment diff --git a/tests/process_test.cpp b/tests/process_test.cpp index 2a63a59..9978a29 100644 --- a/tests/process_test.cpp +++ b/tests/process_test.cpp @@ -118,6 +118,14 @@ int child(std::string_view errand) { os::interrupt::end_as_output_closed(); return 5; // where the system has no such ending } + if (errand == "create") { + // A file created in the directory OS_TEST_DIR names, through what this + // program was given. + const auto dir = os::env::get("OS_TEST_DIR").value_or(""); + auto w = os::fs::write(std::filesystem::path(dir) / "created.txt", "x"); + (void)os::stdio::write_output(w ? "created" : "refused"); + return 0; + } if (errand == "sleep") { std::this_thread::sleep_for(std::chrono::seconds(30)); return 0; @@ -247,6 +255,38 @@ int main(int argc, char** argv) { auto r = os::process::run(o); check(r && r->out == "from-entries", "and reach the program"); } + { + // 0.2.8: the directories a started program receives (openkal 0.23), + // its own directories and one of the test's, the second read-only or + // not: what it may create there follows. + const auto dir = std::filesystem::temp_directory_path() / "mcpplibs-os-grant-probe"; + std::filesystem::create_directories(dir); + std::filesystem::remove(dir / "created.txt"); + auto granted = [&](bool read_only) { + auto o = copy("create"); + o.env = { { "OS_TEST_DIR", os::fs::generic_utf8(dir) } }; + o.cwd = dir; + std::vector gs; + gs.push_back({ os::fs::generic_utf8(dir), dir, read_only }); + for (const auto& part : again) { + const auto parent = os::fs::absolute(std::filesystem::path(part)).parent_path(); + if (std::ranges::none_of(gs, [&](const auto& g) { return os::fs::same(g.path, parent); })) + gs.push_back({ os::fs::generic_utf8(parent), parent, true }); + } + o.grants = gs; + return os::process::run(o); + }; + auto ro = granted(true); + if (!ro && ro.error().code == os::errc::not_supported) { + std::printf(" (this system grants no directories, or cannot hold one read-only: %s)\n", ro.error().message().c_str()); + } else { + check(ro && ro->status.success() && ro->out == "refused", "a program granted a directory read-only creates nothing in it"); + check(!std::filesystem::exists(dir / "created.txt"), "and nothing is there"); + auto rw = granted(false); + check(rw && rw->status.success() && rw->out == "created", "and granted it to write, creates"); + } + std::filesystem::remove(dir / "created.txt"); + } { auto r = os::process::run(copy("relay")); check(r && r->status.success() && r->out.empty() && r->err == "said", From d8be5f9b7234d1c783e52b60720f0db49cb224f0 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 3 Nov 2026 09:13:47 +0900 Subject: [PATCH 2/8] openkal-macos 0.21.2: a sleep suspends on macOS, and the monotonic granularity is the same at every start Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- mcpp.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcpp.toml b/mcpp.toml index a15db54..7f26ac0 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -18,7 +18,7 @@ openkal = "0.23.0" openkal-linux = "0.24.0" [target.macos.dependencies] -openkal-macos = "0.21.0" +openkal-macos = "0.21.2" [target.windows.dependencies] openkal-windows = "0.19.0" From fd97cab7531d116817159f18da455240db2429de Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 3 Nov 2026 11:29:02 +0900 Subject: [PATCH 3/8] ci: mcpp 2026.11.2.3, which compiles the std module with the platform's words Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58b27a7..272bb81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,9 @@ on: env: MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF || '' }} - MCPP_INSTALL: ${{ (github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF) && 'mcpp' || 'mcpp@2026.11.2.1' }} + # 2026.11.2.3: the std module compiled with the platform's words (openkal-platform 0.23.0's + # `-fcf-protection=full` refused every `import std` of an earlier mcpp's). + MCPP_INSTALL: ${{ (github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF) && 'mcpp' || 'mcpp@2026.11.2.3' }} XLINGS_VERSION: v2026.10.26.1 XLINGS_NON_INTERACTIVE: '1' From 88189a2536ae2e475807e80bd1e823af82d1a4ab Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 3 Nov 2026 12:11:43 +0900 Subject: [PATCH 4/8] checks/hosted over openkal-platform 0.23.0; mcpp built from a source ref without the images' MCPP_PLATFORM Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/ci.yml | 20 +++++++++++++++----- checks/hosted/mcpp.toml | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 272bb81..e9fe363 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,9 @@ jobs: src="$RUNNER_TEMP/mcpp-src" git clone --quiet --depth 1 --branch "$MCPP_SOURCE_REF" https://github.com/mcpp-community/mcpp.git "$src" rm -f "$src/.xlings.json" - ( cd "$src" && mcpp build --release ) + # mcpp's own build is not this job's target: MCPP_PLATFORM names the + # platform of the os package's images, and mcpp's graph is another. + ( cd "$src" && env -u MCPP_PLATFORM mcpp build --release ) built=$(find "$src/target" -type f \( -name mcpp -o -name mcpp.exe \) | head -1) [ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; } echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH" @@ -187,7 +189,9 @@ jobs: src="$RUNNER_TEMP/mcpp-src" git clone --quiet --depth 1 --branch "$MCPP_SOURCE_REF" https://github.com/mcpp-community/mcpp.git "$src" rm -f "$src/.xlings.json" - ( cd "$src" && mcpp build --release ) + # mcpp's own build is not this job's target: MCPP_PLATFORM names the + # platform of the os package's images, and mcpp's graph is another. + ( cd "$src" && env -u MCPP_PLATFORM mcpp build --release ) built=$(find "$src/target" -type f \( -name mcpp -o -name mcpp.exe \) | head -1) [ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; } echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH" @@ -245,7 +249,9 @@ jobs: src="$RUNNER_TEMP/mcpp-src" git clone --quiet --depth 1 --branch "$MCPP_SOURCE_REF" https://github.com/mcpp-community/mcpp.git "$src" rm -f "$src/.xlings.json" - ( cd "$src" && mcpp build --release ) + # mcpp's own build is not this job's target: MCPP_PLATFORM names the + # platform of the os package's images, and mcpp's graph is another. + ( cd "$src" && env -u MCPP_PLATFORM mcpp build --release ) built=$(find "$src/target" -type f -name mcpp | head -1) [ -n "$built" ] || { echo "::error::mcpp did not build"; exit 1; } echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH" @@ -312,7 +318,9 @@ jobs: src="$RUNNER_TEMP/mcpp-src" git clone --quiet --depth 1 --branch "$MCPP_SOURCE_REF" https://github.com/mcpp-community/mcpp.git "$src" rm -f "$src/.xlings.json" - ( cd "$src" && mcpp build --release ) + # mcpp's own build is not this job's target: MCPP_PLATFORM names the + # platform of the os package's images, and mcpp's graph is another. + ( cd "$src" && env -u MCPP_PLATFORM mcpp build --release ) built=$(find "$src/target" -type f \( -name mcpp -o -name mcpp.exe \) -path '*/bin/*' | head -1) [ -n "$built" ] || { echo "::error::mcpp did not build"; exit 1; } PATH="$(cd "$(dirname "$built")" && pwd):$PATH" @@ -380,7 +388,9 @@ jobs: src="$RUNNER_TEMP/mcpp-src" git clone --quiet --depth 1 --branch "$MCPP_SOURCE_REF" https://github.com/mcpp-community/mcpp.git "$src" rm -f "$src/.xlings.json" - ( cd "$src" && mcpp build --release ) + # mcpp's own build is not this job's target: MCPP_PLATFORM names the + # platform of the os package's images, and mcpp's graph is another. + ( cd "$src" && env -u MCPP_PLATFORM mcpp build --release ) built=$(find "$src/target" -type f -name mcpp | head -1) [ -n "$built" ] || { echo "::error::mcpp did not build"; exit 1; } echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH" diff --git a/checks/hosted/mcpp.toml b/checks/hosted/mcpp.toml index e62fe2a..41122e5 100644 --- a/checks/hosted/mcpp.toml +++ b/checks/hosted/mcpp.toml @@ -13,7 +13,7 @@ version = "0.1.0" os = { path = "../.." } [target.'cfg(any(linux, macos, windows))'] -platform = "openkal-platform@0.22.0" +platform = "openkal-platform@0.23.0" # The tests are the package's own, each reached by an include from # tests/.cpp here (a test program is a file of the package that runs it). From 9b455bd3dab7d087567547629538c48ce3f39387 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 3 Nov 2026 12:29:42 +0900 Subject: [PATCH 5/8] tests: the directories a granted program was given, shown when what it created is not what it was asked to Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- tests/process_test.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/process_test.cpp b/tests/process_test.cpp index 9978a29..50f2fe5 100644 --- a/tests/process_test.cpp +++ b/tests/process_test.cpp @@ -124,6 +124,10 @@ int child(std::string_view errand) { const auto dir = os::env::get("OS_TEST_DIR").value_or(""); auto w = os::fs::write(std::filesystem::path(dir) / "created.txt", "x"); (void)os::stdio::write_output(w ? "created" : "refused"); + // What it was given, for the starter to show when the outcome is not the one it asked for. + std::string given; + for (const auto& name : os::fs::preopens()) given += "[" + name + "] "; + (void)os::stdio::write_error(given); return 0; } if (errand == "sleep") { @@ -281,6 +285,7 @@ int main(int argc, char** argv) { std::printf(" (this system grants no directories, or cannot hold one read-only: %s)\n", ro.error().message().c_str()); } else { check(ro && ro->status.success() && ro->out == "refused", "a program granted a directory read-only creates nothing in it"); + if (ro && ro->out != "refused") std::printf(" it was given: %s\n", ro->err.c_str()); check(!std::filesystem::exists(dir / "created.txt"), "and nothing is there"); auto rw = granted(false); check(rw && rw->status.success() && rw->out == "created", "and granted it to write, creates"); From d042df2ac2797906eede8f7782b8964ae5b6ca8f Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 3 Nov 2026 12:39:41 +0900 Subject: [PATCH 6/8] tests: the variables a granted program read, beside what it was given Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- tests/process_test.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/process_test.cpp b/tests/process_test.cpp index 50f2fe5..e200711 100644 --- a/tests/process_test.cpp +++ b/tests/process_test.cpp @@ -127,6 +127,9 @@ int child(std::string_view errand) { // What it was given, for the starter to show when the outcome is not the one it asked for. std::string given; for (const auto& name : os::fs::preopens()) given += "[" + name + "] "; + given += "KAL_PREOPENS=" + os::env::get("KAL_PREOPENS").value_or("(none)"); + given += " KAL_GRANT_RIGHTS=" + os::env::get("KAL_GRANT_RIGHTS").value_or("(none)"); + if (auto me = os::process::self()) given += " pid=" + std::to_string(me->id); (void)os::stdio::write_error(given); return 0; } From e7c038baf4bf2ea0e6f278ec13f5df82cd981fa7 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 3 Nov 2026 12:51:45 +0900 Subject: [PATCH 7/8] openkal-macos 0.21.3 and openkal-platform 0.23.1: a hosted program on macOS is given the directories it was granted Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- .github/workflows/ci.yml | 8 ++++---- checks/hosted/mcpp.toml | 2 +- mcpp.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9fe363..2f41fbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -227,9 +227,9 @@ jobs: timeout-minutes: 120 env: # openkal 0.23.0, which this release is written against, is the one - # openkal-platform 0.23.0's version group names; mcpp 2026.11.2.3 and + # openkal-platform 0.23.1's version group names; mcpp 2026.11.2.3 and # later pin it for their openkal rows. - MCPP_PLATFORM: openkal-platform@0.23.0 + MCPP_PLATFORM: openkal-platform@0.23.1 defaults: run: shell: bash @@ -279,9 +279,9 @@ jobs: timeout-minutes: 45 env: # openkal 0.23.0, which this release is written against, is the one - # openkal-platform 0.23.0's version group names; mcpp 2026.11.2.3 and + # openkal-platform 0.23.1's version group names; mcpp 2026.11.2.3 and # later pin it for their openkal rows. - MCPP_PLATFORM: openkal-platform@0.23.0 + MCPP_PLATFORM: openkal-platform@0.23.1 strategy: fail-fast: false matrix: diff --git a/checks/hosted/mcpp.toml b/checks/hosted/mcpp.toml index 41122e5..7309927 100644 --- a/checks/hosted/mcpp.toml +++ b/checks/hosted/mcpp.toml @@ -13,7 +13,7 @@ version = "0.1.0" os = { path = "../.." } [target.'cfg(any(linux, macos, windows))'] -platform = "openkal-platform@0.23.0" +platform = "openkal-platform@0.23.1" # The tests are the package's own, each reached by an include from # tests/.cpp here (a test program is a file of the package that runs it). diff --git a/mcpp.toml b/mcpp.toml index 7f26ac0..3172459 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -18,7 +18,7 @@ openkal = "0.23.0" openkal-linux = "0.24.0" [target.macos.dependencies] -openkal-macos = "0.21.2" +openkal-macos = "0.21.3" [target.windows.dependencies] openkal-windows = "0.19.0" From d678fc68462eeca3b3d38a5ffb786870f7ac71b8 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 3 Nov 2026 17:34:34 +0900 Subject: [PATCH 8/8] ci: validate os against platform 0.23.2 with mcpp 2026.11.3.1 --- .github/workflows/ci.yml | 12 ++++++------ checks/hosted/mcpp.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f41fbe..5eac8f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,9 +35,9 @@ on: env: MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF || '' }} - # 2026.11.2.3: the std module compiled with the platform's words (openkal-platform 0.23.0's + # 2026.11.3.1: the std module compiled with the platform's words (openkal-platform 0.23.0's # `-fcf-protection=full` refused every `import std` of an earlier mcpp's). - MCPP_INSTALL: ${{ (github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF) && 'mcpp' || 'mcpp@2026.11.2.3' }} + MCPP_INSTALL: ${{ (github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF) && 'mcpp' || 'mcpp@2026.11.3.1' }} XLINGS_VERSION: v2026.10.26.1 XLINGS_NON_INTERACTIVE: '1' @@ -227,9 +227,9 @@ jobs: timeout-minutes: 120 env: # openkal 0.23.0, which this release is written against, is the one - # openkal-platform 0.23.1's version group names; mcpp 2026.11.2.3 and + # openkal-platform 0.23.2's version group names; mcpp 2026.11.3.1 and # later pin it for their openkal rows. - MCPP_PLATFORM: openkal-platform@0.23.1 + MCPP_PLATFORM: openkal-platform@0.23.2 defaults: run: shell: bash @@ -279,9 +279,9 @@ jobs: timeout-minutes: 45 env: # openkal 0.23.0, which this release is written against, is the one - # openkal-platform 0.23.1's version group names; mcpp 2026.11.2.3 and + # openkal-platform 0.23.2's version group names; mcpp 2026.11.3.1 and # later pin it for their openkal rows. - MCPP_PLATFORM: openkal-platform@0.23.1 + MCPP_PLATFORM: openkal-platform@0.23.2 strategy: fail-fast: false matrix: diff --git a/checks/hosted/mcpp.toml b/checks/hosted/mcpp.toml index 7309927..56db915 100644 --- a/checks/hosted/mcpp.toml +++ b/checks/hosted/mcpp.toml @@ -13,7 +13,7 @@ version = "0.1.0" os = { path = "../.." } [target.'cfg(any(linux, macos, windows))'] -platform = "openkal-platform@0.23.1" +platform = "openkal-platform@0.23.2" # The tests are the package's own, each reached by an include from # tests/.cpp here (a test program is a file of the package that runs it).