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
66 changes: 61 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,30 @@ jobs:
#
# A check that reads source files must say what they are encoded in, or
# it reports on the runner's locale.
declared = set(re.findall(r'OKW_IMPORT\s+\w+\s+OKW_API\s+(\w+)\s*\(',
open("src/win32.h", encoding="utf-8").read()))
# ⚠️⚠️ EVERY HEADER, AND EVERY SPELLING. THIS CHECK MISSED THE SECOND
# OF EACH AND THE DEFECT IT WAS WRITTEN FOR HAPPENED AGAIN.
#
# It read `src/win32.h' alone and matched `OKW_IMPORT ... OKW_API'
# alone. `src/win.h' declares the object manager's entries in the
# plain form, `__declspec(dllimport) long __stdcall Nt...', so four of
# them were outside what this looked at --- and the failure appeared
# exactly where the comment above says it appeared last time, in
# openkal-llvm-runtime's cross-build, one repository away:
#
# 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 reported a number, the number was of the names it
# knew about, and nothing said the set was partial. So the headers are
# globbed and both forms are matched.
declared = set()
headers = sorted(glob.glob("src/*.h"))
for h in headers:
text = open(h, encoding="utf-8").read()
declared |= set(re.findall(r'OKW_IMPORT\s+\w+\s+OKW_API\s+(\w+)\s*\(', text))
declared |= set(re.findall(
r'__declspec\(dllimport\)[\w\s\*]*?(\w+)\s*\(', text))
exported = set()
for f in glob.glob("port/*.def"):
body = open(f, encoding="utf-8").read().split("EXPORTS", 1)
Expand All @@ -203,10 +225,10 @@ jobs:
print(f"::error::declared={len(declared)} exported={len(exported)}; nothing was compared")
sys.exit(1)
missing = sorted(declared - exported)
print(f" {len(declared)} declared, {len(exported)} exported across "
f"{len(glob.glob('port/*.def'))} .def files")
print(f" {len(declared)} declared across {len(headers)} headers, "
f"{len(exported)} exported across {len(glob.glob('port/*.def'))} .def files")
if missing:
print("::error::declared in src/win32.h and exported by no .def:")
print("::error::declared in src/*.h and exported by no .def:")
for m in missing: print(f" {m}")
sys.exit(1)
print(" ok every declared name is exported")
Expand Down Expand Up @@ -266,6 +288,40 @@ jobs:
git -C .spec checkout --quiet "origin/$branch"
fi

# ⚠️⚠️ CLONING THE SPECIFICATION IS NOT THE SAME AS BUILDING AGAINST IT,
# AND THIS JOB DID THE FIRST WHILE BELIEVING IT DID THE SECOND.
#
# `.spec` above is consumed by `run-conformance.sh`, which substitutes the
# manifest itself and RESTORES IT ON EXIT --- correctly, since a script
# that rewrote a checked-in file and walked away would leave the tree
# holding a path. But the steps AFTER it call `mcpp build` directly, and
# by then the manifest names `openkal` by version again, so those steps
# resolved the PUBLISHED specification:
#
# 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 2026-08-28 across
# the eight repositories of this ecosystem: 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.
- name: Point at the specification's working tree
run: |
set -euo pipefail
# ⚠️ NOT `sed -i'. This step runs on macOS and on Windows too, and
# BSD sed requires an argument to -i that GNU sed refuses. A temporary
# file is the spelling that holds on all three.
subst() { # subst <file> <relative-path-to-the-specification>
sed "s|^openkal = .*$|openkal = { path = \"$2\" }|" "$1" > "$1.next"
mv "$1.next" "$1"
grep -q "path = \"$2\"" "$1" \
|| { echo "::error::$1 was not substituted"; exit 1; }
}
subst mcpp.toml .spec

- name: Install wine
run: |
sudo dpkg --add-architecture i386
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ compile_commands.json
# What a system leaves behind.
.DS_Store
Thumbs.db

# A working tree of the specification, cloned by continuous integration.
# No trailing slash: the pattern must match a symbolic link too.
.spec
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ An implementation of [openkal](https://github.com/mcpplibs/openkal) for Windows.

```toml
[dependencies]
openkal = "0.8.0"
openkal = "0.9.0"

[target.'cfg(windows)'.dependencies]
openkal-windows = "0.3.0"
openkal-windows = "0.4.0"
```

Its purpose is as much to test the specification as to be used. openkal was
Expand Down
4 changes: 2 additions & 2 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-windows"
version = "0.3.0"
version = "0.4.0"
description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol."
license = "Apache-2.0"

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

[dependencies]
openkal = "0.8.0"
openkal = "0.9.0"

# The package contributes definitions and no modules. The interface it
# implements is declared by the specification package, which this package
Expand Down
1 change: 1 addition & 0 deletions port/kernel32.def
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ GetLogicalDriveStringsW
GetProcAddress
GetProcessHeap
GetStdHandle
GetSystemInfo
GetSystemTimePreciseAsFileTime
HeapAlloc
HeapFree
Expand Down
4 changes: 4 additions & 0 deletions port/ntdll.def
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ LIBRARY ntdll.dll
EXPORTS
NtClose
NtCreateFile
NtFlushBuffersFile
NtQueryDirectoryFile
NtQueryInformationFile
NtQueryVolumeInformationFile
NtReadFile
NtSetInformationFile
NtWriteFile
RtlNtStatusToDosError
35 changes: 19 additions & 16 deletions src/datagram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,22 @@ int kal_datagram_local(kal_datagram d, kal_endpoint* out) {
return okw::from_system(ss, *out);
}

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

auto* n = net();
if (n == nullptr) return { 0, kal_err_io };
if (n == nullptr) return -kal_err_io;
ksockaddr_storage ss{};
int addrlen = 0;
if (const int rc = okw::to_system(*to, ss, addrlen); rc != kal_ok)
return { 0, rc };
return -rc;

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

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

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

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

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

if (from != nullptr) {
Expand All @@ -152,7 +155,7 @@ kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
from->port = 0;
}
}
return { static_cast<kal_uintptr>(r), kal_ok };
return static_cast<kal_intptr>(r);
}

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

} // extern "C"
43 changes: 27 additions & 16 deletions src/env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,30 @@ extern "C" {

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

const char* kal_env_arg(kal_uintptr index, kal_uintptr* len) {
// EVERY VALUE IS COPIED INTO THE CALLER'S BUFFER, and each reports the length
// the value HAS. These answered with a pointer into this implementation's own
// storage, which is meaningful only while the implementation shares the
// caller's address space.
namespace {
kal_intptr give(const char* v, kal_uintptr n, char* out, kal_uintptr cap) {
if (out != nullptr && cap != 0) {
const kal_uintptr room = n < cap ? n : cap;
for (kal_uintptr i = 0; i < room; ++i) out[i] = v[i];
}
return static_cast<kal_intptr>(n);
}
} // namespace

kal_intptr kal_env_arg(kal_uintptr index, char* out, kal_uintptr cap) {
prepare();
if (index >= static_cast<kal_uintptr>(g_argc)) { if (len) *len = 0; return nullptr; }
if (len) *len = g_argv_len[index];
return g_argv[index];
if (index >= static_cast<kal_uintptr>(g_argc)) return -kal_err_not_found;
return give(g_argv[index], g_argv_len[index], out, cap);
}

const char* kal_env_var(const char* name, kal_uintptr name_len, kal_uintptr* value_len) {
kal_intptr kal_env_var(const char* name, kal_uintptr name_len,
char* out, kal_uintptr cap) {
prepare();
if (name == nullptr) return -kal_err_invalid;
for (int i = 0; i < g_varc; ++i) {
// Names are compared without regard to case, because that is how this
// environment compares them. A program that set PATH and asked for Path
Expand All @@ -124,23 +139,19 @@ const char* kal_env_var(const char* name, kal_uintptr name_len, kal_uintptr* val
if (a != b) { equal = false; break; }
}
if (!equal) continue;
if (value_len) *value_len = g_value_len[i];
return g_value[i];
return give(g_value[i], g_value_len[i], out, cap);
}
if (value_len) *value_len = 0;
return nullptr;
// A name that is not there is distinct from one whose value is empty.
return -kal_err_not_found;
}

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

const char* kal_env_var_at(kal_uintptr index, kal_uintptr* name_len,
const char** value, kal_uintptr* value_len) {
// The NAME at a position. The value is then obtained by kal_env_var.
kal_intptr kal_env_var_at(kal_uintptr index, char* out, kal_uintptr cap) {
prepare();
if (index >= static_cast<kal_uintptr>(g_varc)) return nullptr;
if (name_len) *name_len = g_name_len[index];
if (value) *value = g_value[index];
if (value_len) *value_len = g_value_len[index];
return g_entry[index];
if (index >= static_cast<kal_uintptr>(g_varc)) return -kal_err_not_found;
return give(g_entry[index], g_name_len[index], out, cap);
}

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

} // extern "C"
Loading
Loading