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
40 changes: 26 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.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.3.1' }}
XLINGS_VERSION: v2026.10.26.1
XLINGS_NON_INTERACTIVE: '1'

Expand Down Expand Up @@ -89,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"
Expand Down Expand Up @@ -185,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"
Expand Down Expand Up @@ -220,10 +226,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.2's version group names; mcpp 2026.11.3.1 and
# later pin it for their openkal rows.
MCPP_PLATFORM: openkal-platform@0.21.0
MCPP_PLATFORM: openkal-platform@0.23.2
defaults:
run:
shell: bash
Expand All @@ -243,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"
Expand All @@ -270,10 +278,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.2's version group names; mcpp 2026.11.3.1 and
# later pin it for their openkal rows.
MCPP_PLATFORM: openkal-platform@0.21.0
MCPP_PLATFORM: openkal-platform@0.23.2
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -310,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"
Expand Down Expand Up @@ -378,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"
Expand Down
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 |
Expand Down
2 changes: 1 addition & 1 deletion checks/hosted/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.23.2"

# The tests are the package's own, each reached by an include from
# tests/<name>.cpp here (a test program is a file of the package that runs it).
Expand Down
12 changes: 6 additions & 6 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand All @@ -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 `<arch>-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.3"

[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"
Expand Down
88 changes: 83 additions & 5 deletions src/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ kal_u64 nanoseconds(std::chrono::milliseconds t) {
return static_cast<kal_u64>(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
Expand Down Expand Up @@ -193,6 +196,11 @@ result<process> process::spawn(const spawn_options& o) {
auto s = std::make_unique<state>();
std::vector<kal_stream> release; // the program's ends, released after the start
std::vector<null_stream> 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);
Expand All @@ -206,6 +214,8 @@ result<process> 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());
Expand All @@ -215,6 +225,8 @@ result<process> 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());
Expand All @@ -224,11 +236,14 @@ result<process> 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());
Expand All @@ -255,9 +270,72 @@ result<process> 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<kal_dir> dirs;
std::vector<kal_file> records;
std::vector<kal_preopen> 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;
Expand Down
Loading
Loading