Skip to content

Commit 2b93aab

Browse files
0.4.0: a platform question for a native build and an image alike; the platform modules in an image
can_run_on(p) and on<P>(f) beside is_<platform>; R5 revised: a platform module is defined where it can be answered, a native build of its platform and an image that asks the system it runs upon (translator, elevated, on_dev_drive, effective_user; translated is false). tests/facts_test runs the questions on every row, natively and as images. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent a1986da commit 2b93aab

12 files changed

Lines changed: 325 additions & 11 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ compile_commands.json
44
*.log
55
mcpp.lock
66

7+
.mcpp/

‎CHANGELOG.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
# Changelog
22

3+
## 0.4.0
4+
5+
- **A platform question for a native build and an image alike.** `can_run_on(p)`
6+
says whether this build can run on `p` (a native build on its own system, an
7+
image on every system a loader runs upon), and `on<P>(f)` runs `f` when the
8+
build runs on `P` now. `if constexpr (can_run_on(P))` keeps another system's
9+
platform module out of a native build, as `if constexpr (is_<platform>)` does;
10+
`host()` then decides in an image, where `is_<platform>` is false and a branch
11+
under it never runs. `on<P>` takes the platform as an argument, so its body is
12+
a template a build that cannot run on `P` never instantiates; a lambda without
13+
the parameter is refused. `is_<platform>` keeps its meaning: a native build of
14+
that platform (README, "A platform question").
15+
- **The platform modules in an image (R5 as revised).** A platform module is
16+
defined where it can be answered: in a native build of its platform, and in an
17+
image where the image can ask the system it runs upon. `linux::translator()`
18+
reads /proc as on Linux; `windows::elevated()` reads the integrity level
19+
`whoami /groups` lists, by SID; `windows::on_dev_drive()` asks `fsutil devdrv
20+
query`; `posix::effective_user()` reads /proc on Linux and `/usr/bin/id -u` on
21+
macOS; `macos::translated()` is `false`, since macOS runs images through an
22+
aarch64 okrun only. The system's programs are started by absolute name with a
23+
bound of ten seconds, and each answer is asked once. The code pages, long
24+
names, the registry, the writable drives and the creation mask remain
25+
undefined in an image: okrun carries an image's streams and names, and the
26+
others belong to a native installation. Before 0.4.0 an image defined no
27+
platform module, and a program shipped as an image that guarded them with
28+
`if constexpr (is_<platform>)` lost them without a word (mcpp-community/mcpp
29+
design 2026-11-08, S2 and S3).
30+
331
## 0.3.1
432

533
- **A name in the long spelling is the name it spells.** `fs::extended` writes

‎README.md‎

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ with compile-time platform constants and a few platform modules.
66

77
```toml
88
[dependencies]
9-
os = { namespace = "mcpplibs", version = "0.3.1" }
9+
mcpplibs.os = "0.4.0"
1010
```
1111

1212
```cpp
@@ -106,7 +106,7 @@ dropped (0.2.8).
106106
107107
| Module | Contents |
108108
|---|---|
109-
| `mcpplibs.os.platform` | `PLATFORM`, `ARCH`, `is_linux` / `is_macos` / `is_windows` / `is_posix` / `is_emscripten`, `exe_suffix`, `path_list_separator`; `host()`, a constant expression on a native target |
109+
| `mcpplibs.os.platform` | `PLATFORM`, `ARCH`, `is_linux` / `is_macos` / `is_windows` / `is_posix` / `is_emscripten`, `exe_suffix`, `path_list_separator`; `host()`, a constant expression on a native target; `can_run_on(p)` and `on<P>(f)`, a platform question that holds in a native build and an image alike |
110110
| `mcpplibs.os.error` | `errc` (openkal's closed set of conditions), `error`, `result<T>` |
111111
| `mcpplibs.os.env` | variables (names without case on Windows), `NAME=value` entries as overrides (`assignments`), arguments, the program's own image, lists of directories in the host's separator (`split_list`, `join_list`), PATH search with PATHEXT |
112112
| `mcpplibs.os.fs` | `write_atomic`, `publish` (no replace), `append`, `sync`, `caps`, `lock`, links, `set_executable`, `stamp_of`, `identity_of`, `owner_of`, `writable`, `locate`, `absolute`, `names_a_place`, `canonical`, a bounded `read`; what a volume does, measured (`measure`, `volume_type`), and the operations that follow it (`place`, `exchange`); names in the host's rules without the file system: `generic`, `native`, `extended`, `is_absolute`, `same`, `within` |
@@ -120,7 +120,7 @@ dropped (0.2.8).
120120
| `mcpplibs.os.machine` | the machine's architecture (through Rosetta and WoW64), memory and the memory available now, processors (logical, physical, of differing speed) |
121121
| `mcpplibs.os.random` | bytes from the system's source of unpredictable bytes; `hex` |
122122
| `mcpplibs.os.library` | shared libraries at run time: `open` (a path or a name the system searches for), `loaded` (one the process has, without loading it), a handle's `symbol` / `function<F>`, `process_symbol` (as the loader resolves one), `suffix` |
123-
| `mcpplibs.os.linux` / `.macos` / `.windows` / `.posix` | platform modules: declared on every target, defined only on their own |
123+
| `mcpplibs.os.linux` / `.macos` / `.windows` / `.posix` | platform modules: declared on every target, defined on their own and, where an image can answer, in an image |
124124
125125
## Rules
126126
@@ -133,9 +133,10 @@ dropped (0.2.8).
133133
- in an image (`<arch>-openkal`) it is an ordinary function, and using it in a constant
134134
expression is a compile error.
135135
- **R4.** A native handle does not appear in a portable type.
136-
- **R5.** A platform module is declared on every target and defined only on its own. A call
137-
outside `if constexpr` for that platform is a link error. The same call in a discarded branch
138-
is not an odr-use, so it links. CI asserts both.
136+
- **R5.** A platform module is declared on every target and defined where it can be answered: in
137+
a native build of its own platform, and in an image where the image can ask the system it runs
138+
upon (0.4.0). Elsewhere a call outside `if constexpr` is a link error. The same call in a
139+
discarded branch is not an odr-use, so it links. CI asserts both.
139140
- **R6.** Where openkal is not in its shared form, this package's resources do not cross a
140141
shared-object boundary.
141142
- **R7.** A name reaches the system as UTF-8 and comes back as UTF-8 on every host, never
@@ -145,11 +146,51 @@ dropped (0.2.8).
145146
nothing else. The tests write, read and rename names in four scripts.
146147
147148
Interface units import `std` and contain no system header and no macro. Implementation units
148-
on openkal import only `openkal.*`. The Windows and macOS units declare the functions they
149+
on openkal import only `openkal.*` and this package's own modules. The Windows and macOS units declare the functions they
149150
call themselves (`src/windows/win32.h`, `src/macos/darwin.h`). Linux's and macOS's kernel
150151
calls are in `src/linux/arch/` and `src/macos/arch/`, which the manifest selects by
151152
architecture.
152153
154+
## A platform question, in a native build and an image
155+
156+
Two questions are asked of the platform, and each has its form:
157+
158+
| Question | Form | Native build | Image |
159+
|---|---|---|---|
160+
| Is this a native build for the platform? | `if constexpr (os::is_windows)` | a constant | `false` |
161+
| Does this build run on the platform now? | `if constexpr (os::can_run_on(P))` with `if (os::host() == P)`, or `os::on<P>([&](auto) { ... })` | a constant; another system's branch is discarded | known when the image is loaded |
162+
163+
The first is right for code that belongs to a native build alone: a console's code page set
164+
for the C library's narrow functions has no meaning in an image, whose streams okrun carries.
165+
The second is right for code that is to take effect wherever the program runs, which is what a
166+
program shipped as an image needs:
167+
168+
```cpp
169+
if constexpr (os::can_run_on(os::platform::windows))
170+
if (os::host() == os::platform::windows)
171+
onDevDrive = os::windows::on_dev_drive(cache);
172+
173+
os::on<os::platform::linux>([&](auto) { sandbox = os::linux::translator().value_or(""); });
174+
```
175+
176+
`on<P>` takes the platform as a parameter, so the body is a template and a build that cannot
177+
run on `P` never instantiates it; a lambda without the parameter is refused, because its body
178+
would name another system's module on every build. The platform modules in an image:
179+
180+
| Function | In an image |
181+
|---|---|
182+
| `linux::translator()` | `/proc/self/status`, the tracer's `comm` and the environment, as on Linux |
183+
| `windows::elevated()` | the integrity level `whoami /groups` lists (SID `S-1-16-12288` or `S-1-16-16384`) |
184+
| `windows::on_dev_drive()` | `fsutil devdrv query <volume>`; not known when the tool answers in another language or refuses |
185+
| `posix::effective_user()` | `/proc/self/status` on Linux, `/usr/bin/id -u` on macOS; the largest number when neither answers |
186+
| `macos::translated()` | `false`: macOS runs images through an aarch64 okrun only |
187+
| `windows::code_page()`, `console_output_code_page()`, `long_paths_enabled()`, `machine_registry_path()`, `writable_fixed_drive()`, `posix::set_creation_mask()` | not defined: okrun carries an image's streams and names, and the others belong to a native installation |
188+
189+
The system's programs are started by absolute name (`%SystemRoot%\System32`, `/usr/bin`), with a
190+
bound of ten seconds, and each answer is asked once per process. `host()` decides which system's
191+
question applies, which is that system's convention; whether a program can be started is
192+
openkal's capability and is answered by the start itself (openkal SPEC clause 30).
193+
153194
## As an ok image
154195

155196
Built for `<arch>-openkal` (openkal SPEC clause 12), a program over this library

‎checks/discarded/src/main.cpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,10 @@ int main() {
1313
if constexpr (os::is_linux) n += static_cast<int>(os::linux::translator().has_value());
1414
if constexpr (os::is_macos) n += static_cast<int>(os::macos::translated());
1515
if constexpr (os::is_windows) n += static_cast<int>(os::windows::elevated());
16+
// The same calls in the form that also holds in an image (0.4.0).
17+
if constexpr (os::can_run_on(os::platform::linux))
18+
if (os::host() == os::platform::linux) n += static_cast<int>(os::linux::translator().has_value());
19+
os::on<os::platform::macos>([&](auto) { n += static_cast<int>(os::macos::translated()); });
20+
os::on<os::platform::windows>([&](auto) { n += static_cast<int>(os::windows::elevated()); });
1621
return n >= 0 ? 0 : 1;
1722
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// facts_test.cpp of mcpplibs.os, compiled in this arrangement. The imports are restated
2+
// because the dependency scan reads this file and not the one it includes.
3+
#include "../../../tests/check.hpp"
4+
import std;
5+
import mcpplibs.os;
6+
#include "../../../tests/facts_test.cpp"

‎checks/hosted/tests/facts_test.cpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// facts_test.cpp of mcpplibs.os, compiled in this arrangement. The imports are restated
2+
// because the dependency scan reads this file and not the one it includes.
3+
#include "../../../tests/check.hpp"
4+
import std;
5+
import mcpplibs.os;
6+
#include "../../../tests/facts_test.cpp"

‎mcpp.toml‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "os"
4-
version = "0.3.1"
4+
version = "0.4.0"
55
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."
66
license = "Apache-2.0"
77
authors = ["mcpplibs"]
@@ -108,10 +108,13 @@ sources = ["src/windows/posix/stdio.cpp"]
108108
sources = ["src/targets/emscripten.cppm", "src/emscripten/*.cpp", "src/posix/fs.cpp"]
109109

110110
# AN OK IMAGE (`<arch>-openkal', openkal SPEC clause 12): no implementation
111-
# beneath --- the loader binds the image's `kal_*' --- and no platform module
112-
# defined. `host()' and the conventions are answered when the image is loaded.
111+
# beneath --- the loader binds the image's `kal_*' --- and the platform modules
112+
# defined where an image can answer them (R5 as revised in 0.4.0): Linux's
113+
# translator from /proc, Windows' token and developer volume and the POSIX
114+
# effective user from the system's own programs, macOS' translation as never.
115+
# `host()' and the conventions are answered when the image is loaded.
113116
[target.'cfg(os = "openkal")'.build]
114-
sources = ["src/targets/openkal.cppm", "src/openkal/*.cpp"]
117+
sources = ["src/targets/openkal.cppm", "src/openkal/*.cpp", "src/linux/linux.cpp"]
115118

116119
# The C library, the C++ run-time and the loader of an image are its
117120
# platform's: mcpp 2026.10.27.1 and later select openkal-platform for

‎src/openkal/macos.cpp‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// mcpplibs.os.macos in an ok image (0.4.0): never translated. macOS runs
2+
// images through an okrun of its own architecture only (aarch64; there is no
3+
// x86-64 okrun for macOS), so an image there is never run by Rosetta. When an
4+
// x86-64 okrun exists, the answer is "an x86-64 image on a machine with
5+
// `hw.optional.arm64'"; `sysctl.proc_translated' would answer for the sysctl
6+
// program asked, not for the image.
7+
module mcpplibs.os.macos;
8+
9+
import std;
10+
11+
namespace mcpplibs::os::macos {
12+
13+
bool translated() { return false; }
14+
15+
} // namespace mcpplibs::os::macos

‎src/openkal/posix.cpp‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// mcpplibs.os.posix in an ok image (0.4.0): the effective user, asked of the
2+
// system the image runs upon. The creation mask is a state of the process that
3+
// openkal does not expose, and remains undefined in an image.
4+
module mcpplibs.os.posix;
5+
6+
import std;
7+
import mcpplibs.os.platform;
8+
import mcpplibs.os.process;
9+
10+
namespace mcpplibs::os::posix {
11+
12+
namespace {
13+
14+
// The second number of the `Uid:' line of /proc/self/status, the effective one.
15+
std::optional<std::uint32_t> from_status() {
16+
std::ifstream in("/proc/self/status", std::ios::binary);
17+
for (std::string line; std::getline(in, line);) {
18+
if (!line.starts_with("Uid:")) continue;
19+
std::istringstream fields(line.substr(4));
20+
std::uint32_t real = 0, effective = 0;
21+
if (fields >> real >> effective) return effective;
22+
}
23+
return std::nullopt;
24+
}
25+
26+
std::optional<std::uint32_t> from_id() {
27+
process::spawn_options o;
28+
o.program = "/usr/bin/id";
29+
o.args = {"-u"};
30+
o.in = process::stdio::null;
31+
o.err = process::stdio::null;
32+
process::bounds limits;
33+
limits.hard = std::chrono::seconds{10};
34+
auto r = process::run(std::move(o), limits);
35+
if (!r || !r->status.success()) return std::nullopt;
36+
std::uint32_t v = 0;
37+
const auto& s = r->out;
38+
const auto [end, ec] = std::from_chars(s.data(), s.data() + s.size(), v);
39+
if (ec != std::errc{} || end == s.data()) return std::nullopt;
40+
return v;
41+
}
42+
43+
} // namespace
44+
45+
// Not known -- not a POSIX system, or neither source answered -- is the largest
46+
// number, which names no user.
47+
std::uint32_t effective_user() {
48+
static const std::uint32_t answer = [] {
49+
std::optional<std::uint32_t> v;
50+
if (host() == platform::linux) v = from_status();
51+
if (!v && (host() == platform::linux || host() == platform::macos)) v = from_id();
52+
return v.value_or(std::numeric_limits<std::uint32_t>::max());
53+
}();
54+
return answer;
55+
}
56+
57+
} // namespace mcpplibs::os::posix

‎src/openkal/windows.cpp‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// mcpplibs.os.windows in an ok image (0.4.0, R5 as revised): an image reaches
2+
// no Win32 function (it loads no library of the system, openkal SPEC clause
3+
// 12), so what it answers it asks a program of the system for. Only the facts
4+
// a program of Windows reports are defined here; the code pages, long names,
5+
// the registry and the drives a user writes to are okrun's affair or the
6+
// native build's, and remain undefined in an image (a use is a link error).
7+
//
8+
// `host()' decides which system's question applies, which is a convention of
9+
// that system and not a capability of openkal's implementation (SPEC clause
10+
// 30); whether a program can be started at all is openkal's capability, and a
11+
// refusal is answered as "not known".
12+
module mcpplibs.os.windows;
13+
14+
import std;
15+
import mcpplibs.os.platform;
16+
import mcpplibs.os.env;
17+
import mcpplibs.os.process;
18+
19+
namespace mcpplibs::os::windows {
20+
21+
namespace {
22+
23+
// `%SystemRoot%\System32\<tool>.exe', never a name looked for on PATH.
24+
std::optional<std::filesystem::path> system_tool(std::string_view tool) {
25+
auto root = env::get("SystemRoot");
26+
if (!root || root->empty()) return std::nullopt;
27+
std::string name(tool);
28+
name += ".exe";
29+
return std::filesystem::path(*root) / "System32" / name;
30+
}
31+
32+
// What the tool printed, when it ran to an end with status 0.
33+
std::optional<std::string> printed(std::string_view tool, std::vector<std::string> args) {
34+
auto program = system_tool(tool);
35+
if (!program) return std::nullopt;
36+
process::spawn_options o;
37+
o.program = *program;
38+
o.args = std::move(args);
39+
o.in = process::stdio::null;
40+
o.err = process::stdio::null;
41+
process::bounds limits;
42+
limits.hard = std::chrono::seconds{10};
43+
auto r = process::run(std::move(o), limits);
44+
if (!r || r->timed_out || !r->status.success()) return std::nullopt;
45+
return std::move(r->out);
46+
}
47+
48+
} // namespace
49+
50+
// The token's integrity level as `whoami /groups' lists it: High (S-1-16-12288)
51+
// or System (S-1-16-16384) is the administrator's token. A SID, not a group's
52+
// name, so the answer does not depend on the system's language.
53+
bool elevated() {
54+
if (host() != platform::windows) return false;
55+
static const bool answer = [] {
56+
const auto text = printed("whoami", {"/groups"});
57+
return text && (text->find("S-1-16-12288") != std::string::npos
58+
|| text->find("S-1-16-16384") != std::string::npos);
59+
}();
60+
return answer;
61+
}
62+
63+
// `fsutil devdrv query <volume>' names a developer volume as one; its answer is
64+
// read only when the tool states one of its two sentences, and is otherwise
65+
// not known (a system in another language, or a user the tool refuses).
66+
std::optional<bool> on_dev_drive(const std::filesystem::path& directory) {
67+
if (host() != platform::windows) return std::nullopt;
68+
const auto text = directory.generic_string();
69+
if (text.size() < 2 || text[1] != ':') return std::nullopt;
70+
const auto said = printed("fsutil", {"devdrv", "query", text.substr(0, 2)});
71+
if (!said) return std::nullopt;
72+
if (said->find("not a developer volume") != std::string::npos) return false;
73+
if (said->find("developer volume") != std::string::npos) return true;
74+
return std::nullopt;
75+
}
76+
77+
} // namespace mcpplibs::os::windows

0 commit comments

Comments
 (0)