Skip to content

Commit 0c8537c

Browse files
0.4.0 — adopt openkal 0.9, and report the coarser of this system's two granularities (#15)
* 0.4.0 --- adopt openkal 0.9, and report the coarser of this system's two granularities ⭐⭐ THIS SYSTEM HAS TWO MEMORY QUANTA AND IS THE REASON THE SPECIFICATION ASKS FOR ONE NUMBER RATHER THAN A PAGE SIZE. It protects memory in pages of four kilobytes and RESERVES it in units of sixty-four, so a value taken from either alone is wrong for the other. What `kal_memory_granularity' promises is that an address and a length that are multiples of it are acceptable to every operation of the interface, and the coarser of the two is the only answer that keeps that promise. A specification that had derived the number from the page size of one family of systems would have been wrong here. `kal_fs_props' takes the directory and asks this environment what the volume can do, which it reports itself --- so nothing is guessed: case sensitivity is read rather than assumed absent, and a volume that holds nodes whose content is another name is reported as holding them. ⚠️ Those nodes are REPORTED and are not MADE, and the enquiry says so before a caller tries. Creating one here requires a privilege an ordinary program does not hold or the system's developer mode, and reading one requires a control code this implementation does not yet issue. Neither is claimed, and both operations refuse rather than being absent, because a caller can ask first --- which is what makes the refusal admissible rather than the defect clause 6.2 names. The rest follows the specification: transfers return one signed word; the parameters and names are copied into the caller's buffer; `kal_node_info' carries its own size, what was filled, and an identity taken from the volume's serial number and the file's index --- two words because this environment's index is unique WITHIN a volume and one word would make two files on two volumes read as one; asking resolves a node unless told not to; typed stream handles; `kal_version' and `kal_interfaces'. Ninety-three names are exported and none other: the two `openkal.space' names are absent, which is what the word reports as well. * README: the versions it names are the versions that exist Every README here opens by showing what a program writes in its manifest, which is the first thing a reader copies and the last thing anyone edits. These lines had drifted --- the specification's own README asked for a version four minor releases old --- and nothing checked them. `openkal/tools/check-readme-versions.sh` now does. * ci: build against the specification under review, not the published one A job that resolves the published specification cannot review a change to one. Another job in this file already cloned the specification and substituted it, and that made the arrangement look complete. It was not: the steps below reached openkal by version, so a version under review -- which by definition is not published -- failed them with E_NOT_FOUND: package 'compat.openkal@0.9.0' not found in the synced index ... the index is current, so this name is either wrong or not published yet The unit is the job, not the repository. Measured across the eight repositories of this ecosystem while one change spanned all of them: seven jobs in three of them had this shape, and each of those repositories also had a job doing it correctly -- which is what made the gap invisible to a check done a repository at a time. These steps are green on main and can only be green there, because there the published version is the one under test. It is not a check that fails, it is a check that cannot run at the only time it would have something to say. Also: GetSystemInfo is declared in src/win32.h and was exported by no .def, so the completeness check named it. It belongs to kernel32. * ci: substitute the specification per step, not per job Measured 2026-08-28 across the eight repositories of this ecosystem while one change spanned all of them: eight jobs in four of them called `mcpp build' at a point where the manifest still named openkal BY VERSION, so a version under review -- which by definition is not published -- failed them with E_NOT_FOUND. The mechanism is not a missing substitution. run-conformance.sh substitutes the manifest and RESTORES IT ON EXIT, correctly; every step after it is back to naming a version. So an audit asking "does this job substitute?" passes the job and misses the steps, which is how the first pass at this found three repositories and not four. These steps are green on main and can only be green there, because there the published version is the one under test. It is not a check that fails, it is a check that cannot run at the only time it would have something to say. The substitution is also portable now: the opensbi and uefi portability jobs run on macOS and Windows, where BSD sed requires an argument to -i that GNU sed refuses. * fix: an enquiry that reports an overflow has still answered FILE_FS_VOLUME_INFORMATION ends in the volume's label, which is as long as the label is. The buffer here held the fixed part and one character of it -- enough for every field this reads, since the serial number precedes the label -- and the object manager still reported STATUS_BUFFER_OVERFLOW because the label did not fit. That value is 0x80000005: negative, so okw::ok said no, so the identity position was left clear. Which is a correct report of something that was not true. This implementation was saying "this node's identity is not known here", a caller was believing it, and the identity was sitting in the buffer. It surfaced two packages away, in openkal-musl's probe on Windows: "two different files have different identities" did not hold, because both had been given the zero this branch leaves behind. The conformance suite could not have said so and is right not to -- an implementation is allowed to decline the field, so the suite reports the observation as one it did not make. Room for a label is given so the ordinary case succeeds, and the overflow is accepted so the extraordinary one still answers. * fix: the .def check read one header and one spelling, and there are two of each The check's own comment records the defect it was written to prevent: three declarations added for openkal 0.8 without matching .def lines, and the failure appearing one repository away in openkal-llvm-runtime's cross-build. It happened again, with the same signature: ld.lld: error: undefined symbol: __declspec(dllimport) NtQueryVolumeInformationFile The check was right and its SCOPE was wrong, which is the harder kind to notice: it read src/win32.h alone and matched `OKW_IMPORT ... OKW_API' alone, while src/win.h declares the object manager's entries in the plain `__declspec(dllimport) long __stdcall Nt...' form. It reported a number, the number was of the names it knew about, and nothing said the set was partial. It now globs src/*.h and matches both forms: 58 declared across four headers rather than 49 across one. Four names were outside it -- NtFlushBuffersFile, NtQueryVolumeInformationFile, NtReadFile, NtWriteFile -- and all four are now exported. Only one of them was referenced, which is why only one broke a link; the other three were the same latent hazard. Verified locally by cross-building openkal-llvm-runtime's same-source example for x86_64-windows-gnu and aarch64-macos over these working trees. --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent 2152d0a commit 0c8537c

21 files changed

Lines changed: 442 additions & 121 deletions

.github/workflows/ci.yml

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,30 @@ jobs:
189189
#
190190
# A check that reads source files must say what they are encoded in, or
191191
# it reports on the runner's locale.
192-
declared = set(re.findall(r'OKW_IMPORT\s+\w+\s+OKW_API\s+(\w+)\s*\(',
193-
open("src/win32.h", encoding="utf-8").read()))
192+
# ⚠️⚠️ EVERY HEADER, AND EVERY SPELLING. THIS CHECK MISSED THE SECOND
193+
# OF EACH AND THE DEFECT IT WAS WRITTEN FOR HAPPENED AGAIN.
194+
#
195+
# It read `src/win32.h' alone and matched `OKW_IMPORT ... OKW_API'
196+
# alone. `src/win.h' declares the object manager's entries in the
197+
# plain form, `__declspec(dllimport) long __stdcall Nt...', so four of
198+
# them were outside what this looked at --- and the failure appeared
199+
# exactly where the comment above says it appeared last time, in
200+
# openkal-llvm-runtime's cross-build, one repository away:
201+
#
202+
# ld.lld: error: undefined symbol:
203+
# __declspec(dllimport) NtQueryVolumeInformationFile
204+
#
205+
# ⭐ THE CHECK WAS RIGHT AND ITS SCOPE WAS WRONG, which is the harder
206+
# kind to notice: it reported a number, the number was of the names it
207+
# knew about, and nothing said the set was partial. So the headers are
208+
# globbed and both forms are matched.
209+
declared = set()
210+
headers = sorted(glob.glob("src/*.h"))
211+
for h in headers:
212+
text = open(h, encoding="utf-8").read()
213+
declared |= set(re.findall(r'OKW_IMPORT\s+\w+\s+OKW_API\s+(\w+)\s*\(', text))
214+
declared |= set(re.findall(
215+
r'__declspec\(dllimport\)[\w\s\*]*?(\w+)\s*\(', text))
194216
exported = set()
195217
for f in glob.glob("port/*.def"):
196218
body = open(f, encoding="utf-8").read().split("EXPORTS", 1)
@@ -203,10 +225,10 @@ jobs:
203225
print(f"::error::declared={len(declared)} exported={len(exported)}; nothing was compared")
204226
sys.exit(1)
205227
missing = sorted(declared - exported)
206-
print(f" {len(declared)} declared, {len(exported)} exported across "
207-
f"{len(glob.glob('port/*.def'))} .def files")
228+
print(f" {len(declared)} declared across {len(headers)} headers, "
229+
f"{len(exported)} exported across {len(glob.glob('port/*.def'))} .def files")
208230
if missing:
209-
print("::error::declared in src/win32.h and exported by no .def:")
231+
print("::error::declared in src/*.h and exported by no .def:")
210232
for m in missing: print(f" {m}")
211233
sys.exit(1)
212234
print(" ok every declared name is exported")
@@ -266,6 +288,40 @@ jobs:
266288
git -C .spec checkout --quiet "origin/$branch"
267289
fi
268290
291+
# ⚠️⚠️ CLONING THE SPECIFICATION IS NOT THE SAME AS BUILDING AGAINST IT,
292+
# AND THIS JOB DID THE FIRST WHILE BELIEVING IT DID THE SECOND.
293+
#
294+
# `.spec` above is consumed by `run-conformance.sh`, which substitutes the
295+
# manifest itself and RESTORES IT ON EXIT --- correctly, since a script
296+
# that rewrote a checked-in file and walked away would leave the tree
297+
# holding a path. But the steps AFTER it call `mcpp build` directly, and
298+
# by then the manifest names `openkal` by version again, so those steps
299+
# resolved the PUBLISHED specification:
300+
#
301+
# E_NOT_FOUND: package 'compat.openkal@0.9.0' not found in the synced
302+
# index ... the index is current, so this name is either wrong or not
303+
# published yet
304+
#
305+
# ⭐⭐ THE UNIT IS THE JOB, NOT THE REPOSITORY. Measured 2026-08-28 across
306+
# the eight repositories of this ecosystem: seven jobs in three of them
307+
# had this shape, and each of those repositories ALSO had a job doing it
308+
# correctly --- which is what made the gap invisible to a check done a
309+
# repository at a time. These steps are green on `main` and can only be
310+
# green there, because there the published version IS the one under test.
311+
- name: Point at the specification's working tree
312+
run: |
313+
set -euo pipefail
314+
# ⚠️ NOT `sed -i'. This step runs on macOS and on Windows too, and
315+
# BSD sed requires an argument to -i that GNU sed refuses. A temporary
316+
# file is the spelling that holds on all three.
317+
subst() { # subst <file> <relative-path-to-the-specification>
318+
sed "s|^openkal = .*$|openkal = { path = \"$2\" }|" "$1" > "$1.next"
319+
mv "$1.next" "$1"
320+
grep -q "path = \"$2\"" "$1" \
321+
|| { echo "::error::$1 was not substituted"; exit 1; }
322+
}
323+
subst mcpp.toml .spec
324+
269325
- name: Install wine
270326
run: |
271327
sudo dpkg --add-architecture i386

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ compile_commands.json
1717
# What a system leaves behind.
1818
.DS_Store
1919
Thumbs.db
20+
21+
# A working tree of the specification, cloned by continuous integration.
22+
# No trailing slash: the pattern must match a symbolic link too.
23+
.spec

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ An implementation of [openkal](https://github.com/mcpplibs/openkal) for Windows.
44

55
```toml
66
[dependencies]
7-
openkal = "0.8.0"
7+
openkal = "0.9.0"
88

99
[target.'cfg(windows)'.dependencies]
10-
openkal-windows = "0.3.0"
10+
openkal-windows = "0.4.0"
1111
```
1212

1313
Its purpose is as much to test the specification as to be used. openkal was

mcpp.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-windows"
4-
version = "0.3.0"
4+
version = "0.4.0"
55
description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol."
66
license = "Apache-2.0"
77

@@ -18,7 +18,7 @@ authors = ["mcpplibs"]
1818
repo = "https://github.com/mcpplibs/openkal-windows"
1919

2020
[dependencies]
21-
openkal = "0.8.0"
21+
openkal = "0.9.0"
2222

2323
# The package contributes definitions and no modules. The interface it
2424
# implements is declared by the specification package, which this package

port/kernel32.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ GetLogicalDriveStringsW
2727
GetProcAddress
2828
GetProcessHeap
2929
GetStdHandle
30+
GetSystemInfo
3031
GetSystemTimePreciseAsFileTime
3132
HeapAlloc
3233
HeapFree

port/ntdll.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ LIBRARY ntdll.dll
77
EXPORTS
88
NtClose
99
NtCreateFile
10+
NtFlushBuffersFile
1011
NtQueryDirectoryFile
1112
NtQueryInformationFile
13+
NtQueryVolumeInformationFile
14+
NtReadFile
1215
NtSetInformationFile
16+
NtWriteFile
1317
RtlNtStatusToDosError

src/datagram.cpp

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,22 @@ int kal_datagram_local(kal_datagram d, kal_endpoint* out) {
7777
return okw::from_system(ss, *out);
7878
}
7979

80-
kal_io_result kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr len,
81-
const kal_endpoint* to) {
80+
kal_intptr kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr len,
81+
const kal_endpoint* to) {
8282
const SOCKET s = socket_of(d);
83-
if (bad(s) || to == nullptr) return { 0, kal_err_invalid };
84-
if (len > kMaxOne) return { 0, kal_err_invalid };
83+
if (bad(s) || to == nullptr) return -kal_err_invalid;
84+
if (len > kMaxOne) return -kal_err_invalid;
8585

8686
auto* n = net();
87-
if (n == nullptr) return { 0, kal_err_io };
87+
if (n == nullptr) return -kal_err_io;
8888
ksockaddr_storage ss{};
8989
int addrlen = 0;
9090
if (const int rc = okw::to_system(*to, ss, addrlen); rc != kal_ok)
91-
return { 0, rc };
91+
return -rc;
9292

9393
const int r = n->send_to(s, static_cast<const char*>(buf), static_cast<int>(len),
9494
0, &ss, addrlen);
95-
if (r < 0) return { 0, okw::last_socket_error() };
95+
if (r < 0) return -okw::last_socket_error();
9696

9797
// A MESSAGE IS SENT WHOLE OR NOT AT ALL, which is what this interface
9898
// states. The system reports a count anyway; a count short of the length
@@ -101,17 +101,17 @@ kal_io_result kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr
101101
// partial send this interface says cannot occur, so it is reported as a
102102
// failure of the medium instead.
103103
const kal_uintptr sent = static_cast<kal_uintptr>(r);
104-
return { sent, sent == len ? kal_ok : kal_err_io };
104+
return sent == len ? static_cast<kal_intptr>(sent) : -kal_err_io;
105105
}
106106

107-
kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
108-
kal_endpoint* from) {
107+
kal_intptr kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
108+
kal_endpoint* from) {
109109
const SOCKET s = socket_of(d);
110-
if (bad(s)) return { 0, kal_err_invalid };
110+
if (bad(s)) return -kal_err_invalid;
111111
if (len > kMaxOne) len = kMaxOne;
112112

113113
auto* n = net();
114-
if (n == nullptr) return { 0, kal_err_io };
114+
if (n == nullptr) return -kal_err_io;
115115
ksockaddr_storage ss{};
116116
int addrlen = static_cast<int>(sizeof ss);
117117

@@ -137,9 +137,12 @@ kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
137137
from->addr_len = 0;
138138
from->port = 0;
139139
}
140-
return { len, kal_ok };
140+
// A message longer than the buffer: the bytes placed are the
141+
// buffer's length, and reporting the count is reporting what the
142+
// caller may read.
143+
return static_cast<kal_intptr>(len);
141144
}
142-
return { 0, okw::last_socket_error() };
145+
return -okw::last_socket_error();
143146
}
144147

145148
if (from != nullptr) {
@@ -152,7 +155,7 @@ kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
152155
from->port = 0;
153156
}
154157
}
155-
return { static_cast<kal_uintptr>(r), kal_ok };
158+
return static_cast<kal_intptr>(r);
156159
}
157160

158161
void kal_datagram_close(kal_datagram d) {
@@ -166,6 +169,6 @@ void kal_datagram_close(kal_datagram d) {
166169
// been set, and this interface has no operation that would set it; a word
167170
// claiming a facility no operation reaches is the disagreement clause 6.2 exists
168171
// to prevent.
169-
const kal_uintptr kal_datagram_props = KAL_DGRAM_PROP_IPV6;
172+
kal_uintptr kal_datagram_props(void) { return KAL_DGRAM_PROP_IPV6; }
170173

171174
} // extern "C"

src/env.cpp

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,30 @@ extern "C" {
101101

102102
kal_uintptr kal_env_arg_count(void) { prepare(); return static_cast<kal_uintptr>(g_argc); }
103103

104-
const char* kal_env_arg(kal_uintptr index, kal_uintptr* len) {
104+
// EVERY VALUE IS COPIED INTO THE CALLER'S BUFFER, and each reports the length
105+
// the value HAS. These answered with a pointer into this implementation's own
106+
// storage, which is meaningful only while the implementation shares the
107+
// caller's address space.
108+
namespace {
109+
kal_intptr give(const char* v, kal_uintptr n, char* out, kal_uintptr cap) {
110+
if (out != nullptr && cap != 0) {
111+
const kal_uintptr room = n < cap ? n : cap;
112+
for (kal_uintptr i = 0; i < room; ++i) out[i] = v[i];
113+
}
114+
return static_cast<kal_intptr>(n);
115+
}
116+
} // namespace
117+
118+
kal_intptr kal_env_arg(kal_uintptr index, char* out, kal_uintptr cap) {
105119
prepare();
106-
if (index >= static_cast<kal_uintptr>(g_argc)) { if (len) *len = 0; return nullptr; }
107-
if (len) *len = g_argv_len[index];
108-
return g_argv[index];
120+
if (index >= static_cast<kal_uintptr>(g_argc)) return -kal_err_not_found;
121+
return give(g_argv[index], g_argv_len[index], out, cap);
109122
}
110123

111-
const char* kal_env_var(const char* name, kal_uintptr name_len, kal_uintptr* value_len) {
124+
kal_intptr kal_env_var(const char* name, kal_uintptr name_len,
125+
char* out, kal_uintptr cap) {
112126
prepare();
127+
if (name == nullptr) return -kal_err_invalid;
113128
for (int i = 0; i < g_varc; ++i) {
114129
// Names are compared without regard to case, because that is how this
115130
// environment compares them. A program that set PATH and asked for Path
@@ -124,23 +139,19 @@ const char* kal_env_var(const char* name, kal_uintptr name_len, kal_uintptr* val
124139
if (a != b) { equal = false; break; }
125140
}
126141
if (!equal) continue;
127-
if (value_len) *value_len = g_value_len[i];
128-
return g_value[i];
142+
return give(g_value[i], g_value_len[i], out, cap);
129143
}
130-
if (value_len) *value_len = 0;
131-
return nullptr;
144+
// A name that is not there is distinct from one whose value is empty.
145+
return -kal_err_not_found;
132146
}
133147

134148
kal_uintptr kal_env_var_count(void) { prepare(); return static_cast<kal_uintptr>(g_varc); }
135149

136-
const char* kal_env_var_at(kal_uintptr index, kal_uintptr* name_len,
137-
const char** value, kal_uintptr* value_len) {
150+
// The NAME at a position. The value is then obtained by kal_env_var.
151+
kal_intptr kal_env_var_at(kal_uintptr index, char* out, kal_uintptr cap) {
138152
prepare();
139-
if (index >= static_cast<kal_uintptr>(g_varc)) return nullptr;
140-
if (name_len) *name_len = g_name_len[index];
141-
if (value) *value = g_value[index];
142-
if (value_len) *value_len = g_value_len[index];
143-
return g_entry[index];
153+
if (index >= static_cast<kal_uintptr>(g_varc)) return -kal_err_not_found;
154+
return give(g_entry[index], g_name_len[index], out, cap);
144155
}
145156

146157
}

src/exec.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ void kal_exec_free(void* p, kal_uintptr size) {
6666
// protection call is not one-way, and a second `VirtualProtect' to
6767
// PAGE_READWRITE succeeds. The position is set accordingly, and a caller that
6868
// must change published bytes need not abandon the region.
69-
const kal_uintptr kal_exec_props = KAL_EXEC_PROP_REPUBLISH;
69+
// This system grants executable memory to every program; nothing here is
70+
// withheld from an artifact for the way it was produced.
71+
kal_uintptr kal_exec_props(void) {
72+
return KAL_EXEC_PROP_REPUBLISH | KAL_EXEC_PROP_AVAILABLE;
73+
}
7074

7175
} // extern "C"

0 commit comments

Comments
 (0)