Skip to content

Commit 844c2c5

Browse files
0.7.0 — adopt openkal 0.9, and make the aarch64 leg work for the first time (#18)
* 0.7.0 --- adopt openkal 0.9, and answer per resource what was answered per implementation Every report becomes an operation, so an implementation reports what is true of the resource the caller named rather than one word for the whole machine. The change that has teeth is `kal_fs_props`: * it took no argument and claimed KAL_FS_PROP_CASE_SENSITIVE unconditionally. This implementation offers the whole filesystem as a preopen, so a FAT volume mounted anywhere on the machine is reachable through it, and the claim is false there. It now consults the format the resource is on; * it never claimed KAL_FS_PROP_LINKS while every operation here met them. ⚠️ AND ASKING NOW RESOLVES A LINK, BECAUSE OPENING ALWAYS DID. `kal_fs_info` passed AT_SYMLINK_NOFOLLOW always while `kal_fs_open` did not set O_NOFOLLOW --- the constant was declared in sys.h and used nowhere. So in one program, opening a name reached a file and asking about it reported a link. A C library above this reported `is_regular_file` as false for a name whose bytes it could read, and one such node made a whole tree uncopyable. The rest follows the specification: * transfers return one signed word; * `kal_env_*` and `kal_fs_preopen` and `kal_fs_list_next` copy into the caller's buffer and report the length the value has; * `kal_node_info` carries its own size, reports what was filled, and carries the device and inode as an opaque identity a caller may compare and not read --- which is what stops two different files reading as one; * `kal_fs_link_create` and `kal_fs_link_read` over symlinkat and readlinkat, with availability answered by the enquiry before they are called; * `kal_memory_granularity` from the auxiliary vector rather than a constant; * `kal_fs_max_name`, because a bound a caller cannot learn produces a failure it cannot attribute --- a longer name was refused as `kal_err_invalid`, which is also the answer for a name that ascends; * `kal_fs_stream` and `kal_spawn_streams` carry their type; * `kal_version` and `kal_interfaces`, answered with constants. 168 conformance observations hold, up from 143. * aarch64 used x86_64's numbers, so no directory opened and no file had a kind Two defects, both from one habit: a constant that differs between architectures written once, as though it did not. ⚠️⚠️ THE OPEN FLAGS. `O_DIRECTORY`, `O_NOFOLLOW` and `O_DIRECT` have one set of values on x86_64 and another in the kernel's architecture-independent header, which is what aarch64 uses: x86_64 aarch64 O_DIRECTORY 0200000 040000 O_NOFOLLOW 0400000 0100000 O_DIRECT 040000 0200000 So on aarch64 this asked for O_DIRECT where it meant O_DIRECTORY, and the kernel refuses O_DIRECT on a directory. EVERY DIRECTORY FAILED TO OPEN --- including the two preopens supplied at inception, which are every directory a program above this can reach. Measured: `kal_fs_preopen_count' answered two and both entries reported `kal_err_permission' with a handle of zero. A C library above then had nothing to resolve a name against, so every `open' answered ENOENT and `getcwd' answered "/", which reads as a program started somewhere odd rather than as an implementation that opened nothing. ⚠️⚠️ THE STAT RECORD. The kernel's architecture-independent `struct stat' places the mode immediately after the device and inode; the layout here was neither that nor x86_64's. The mode was read from offset 60, where the kernel writes a group, and the size from 32, where it writes a device number. Measured, the same program on both: x86_64 file: kind=1 size=10 writable=1 link: kind=3 aarch64 file: kind=4 size=0 writable=0 link: kind=4 Every node on aarch64 was "some other kind of thing", of length zero and not writable --- so a C library reported that a file it had just written ten bytes to was not a regular file, and every operation that decides upon a kind decided wrongly. ⭐ THE OFFSETS ARE NOW ASSERTED. A field read from the wrong offset is a wrong answer and not a failure, so the build could not report it; now it can. ⚠️ AND NOTHING IN THIS ECOSYSTEM COULD HAVE CAUGHT EITHER. The conformance suite runs on the machine that builds it, and every hosted machine in this ecosystem's continuous integration is x86_64 or an arm64 Mac --- which uses openkal-macos and a different record again. The aarch64 leg of this implementation was built and never run. Both were found by running it: under qemu-aarch64 the port's own POSIX probe reported seven failures where x86_64 reported none, and after these two it reports one, which is the emulator's own handling of a program that starts another. * The package's own tests, and a README that names versions which exist ⚠️ THE TESTS WERE NOT UPDATED AND CI IS WHERE THAT SHOWED. Seven of them use the surface directly and every one of them failed to compile against 0.9 --- which is the arrangement working: a package that changes an interface and does not change what examines it has changed nothing that anybody checked. One of them then failed at RUN rather than at compile, and the reason is worth recording: the enquiry that reports a preopen's name now copies it into the caller's buffer, so a call site that passed a null buffer and then read the name compiled and dereferenced zero. The end of an enumeration is likewise the iterator becoming zero rather than a pointer becoming null, because there is no pointer any more. ⚠️⚠️ AND THE README TOLD A READER TO ASK FOR A VERSION FOUR MINOR RELEASES OLD. 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. The specification's own said `openkal = "0.5.1"` while the package was at 0.9.0. ⭐ The staleness is not the finding. That it was INVISIBLE is: the surface is checked against SURFACE.txt, the declarations against both forms, the behaviour against the conformance suite --- and the one line a reader actually types was checked by nobody. `tools/check-readme-versions.sh` in the specification's repository now checks it, and reports `?` rather than passing where it cannot look, so its "no" and its "did not run" do not read the same. 7 passed, 0 failed. * fix: one C library passes the vectors to .init_array and the other does not glibc calls every .init_array entry with (argc, argv, envp). musl calls them with NO ARGUMENTS. src/env.cpp declared a constructor taking three and recorded what arrived, which under musl is whatever the argument registers happened to hold -- so every program above this implementation and a musl C library was told its argument count was a text address, and the first enquiry after the count dereferenced a small integer as a pointer. Found by running this package's tests for aarch64, where it reads as an architecture defect. It is not, and the control that separates the two is x86_64-linux-musl: target argc argv envp x86_64-linux-gnu 1 <stack> <stack> x86_64-linux-musl 0x4004c2 1 <stack> aarch64-linux-musl 0x405ee4 1 <stack> Both musl rows are shifted by one and the glibc row is not, so the axis is the C library and not the machine. Running only aarch64 would have attributed it to the machine, and the fix would have been wrong. The arguments are now checked rather than believed -- argv[argc] must be the terminator, argc must be a count -- and where they do not hold the vectors are recovered from `environ' by walking back over argv's terminator to the slot that holds the count, which must equal the number of entries actually found. Where even that does not hold, nothing is recorded and the program is told it has no arguments, which is an answer rather than a fault. `environ' is a WEAK reference and the independence check now says so as a rule rather than as an exception: this one name is permitted only when its type letter is weak. It is admissible where `puts' is not because a call into the runtime a program supplied can re-enter this implementation without bound and a pointer executes nothing, and because weak means a program with no C library still links. A strong reference to the same name would make one required silently, and the check now fails on it -- measured, both ways. Verified: 7 tests pass on x86_64-linux-gnu, x86_64-linux-musl and aarch64-linux-musl. --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent 98ee440 commit 844c2c5

25 files changed

Lines changed: 661 additions & 193 deletions

‎.github/workflows/ci.yml‎

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,14 +265,41 @@ jobs:
265265
# __libc_start_main and main are the hand-over, and are undefined here
266266
# by construction.
267267
permitted='^(memcpy|memmove|memset|memcmp|__libc_start_main|main|_GLOBAL_OFFSET_TABLE_|kal_[a-z_]+|__init_array_start|__init_array_end|__preinit_array_start|__preinit_array_end|_ZN3okl.*)$'
268+
269+
# ⭐⭐ ONE NAME IS PERMITTED ONLY IF IT IS WEAK, AND THE WEAKNESS IS
270+
# THE WHOLE OF THE PERMISSION.
271+
#
272+
# `environ' is how src/env.cpp recovers the vectors the kernel placed
273+
# on the stack when the C library above did not pass them --- glibc
274+
# calls every `.init_array' entry with (argc, argv, envp) and musl
275+
# calls them with none, so what arrived there was register residue.
276+
#
277+
# It is admissible where `puts' is not, and the difference is not that
278+
# it is smaller. This check exists because a CALL into the runtime a
279+
# program supplied resolves to the program's and can re-enter this
280+
# implementation without bound. A pointer executes nothing. And being
281+
# WEAK it is null in a program that has no C library, so it does not
282+
# make one required --- which a strong reference to the same name
283+
# would, silently, and is why the type letter is checked and not just
284+
# the name.
285+
weak_permitted='^environ$'
268286
bad=0
269-
for s in $(nm --undefined-only $objs | awk '{print $2}' | sort -u); do
270-
[ -n "$s" ] || continue
271-
if ! printf '%s\n' "$s" | grep -qE "$permitted"; then
272-
echo "the implementation references a symbol it must not: $s" >&2
273-
bad=1
287+
nm --undefined-only $objs | awk '{ print $1, $2 }' | sort -u |
288+
while read -r kind name; do
289+
[ -n "$name" ] || continue
290+
printf '%s\n' "$name" | grep -qE "$permitted" && continue
291+
if printf '%s\n' "$name" | grep -qE "$weak_permitted"; then
292+
case "$kind" in
293+
w|v) continue ;;
294+
*) echo "::error::$name is permitted only as a weak reference, and this one is '$kind'" >&2 ;;
295+
esac
296+
else
297+
echo "the implementation references a symbol it must not: $name" >&2
274298
fi
299+
echo bad >> "$RUNNER_TEMP/independence.bad"
275300
done
301+
[ -s "$RUNNER_TEMP/independence.bad" ] && bad=1
302+
rm -f "$RUNNER_TEMP/independence.bad"
276303
test "$bad" -eq 0
277304
echo "the implementation references no C library symbol"
278305

‎.gitignore‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@ compile_commands.json
1919
Thumbs.db
2020

2121
# The specification tree tools/run-conformance.sh clones beside the sources.
22-
.spec/
22+
#
23+
# ⚠️ NO TRAILING SLASH. `.spec/' matches a directory and does not match a
24+
# SYMBOLIC LINK to one, which is what a working checkout naturally has; the link
25+
# was consequently committed once, pointing at a path that exists on one
26+
# machine.
27+
.spec

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ for Linux, written on the kernel's own system-call interface.
55

66
```toml
77
[dependencies]
8-
openkal = "0.5.1"
8+
openkal = "0.9.0"
99

1010
[target.'cfg(os = "linux")'.dependencies]
11-
openkal-linux = "0.5.1"
11+
openkal-linux = "0.7.0"
1212
```
1313

1414
## Why it does not use a C library

‎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-linux"
4-
version = "0.6.0"
4+
version = "0.7.0"
55
description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one."
66
license = "Apache-2.0"
77

@@ -18,7 +18,7 @@ authors = ["mcpplibs"]
1818
repo = "https://github.com/mcpplibs/openkal-linux"
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

‎src/datagram.cpp‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,23 @@ int kal_datagram_local(kal_datagram d, kal_endpoint* out) {
7171
return okl::from_kernel(ss, *out);
7272
}
7373

74-
kal_io_result kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr len,
75-
const kal_endpoint* to) {
74+
kal_intptr kal_datagram_send_to(kal_datagram d, const void* buf, kal_uintptr len,
75+
const kal_endpoint* to) {
7676
const int fd = fd_of(d);
77-
if (fd < 0 || to == nullptr) return { 0, kal_err_invalid };
77+
if (fd < 0 || to == nullptr) return -kal_err_invalid;
7878

7979
okl::ksockaddr_storage ss{};
8080
okl_long addrlen = 0;
8181
if (const int rc = okl::to_kernel(*to, ss, addrlen); rc != kal_ok)
82-
return { 0, rc };
82+
return -rc;
8383

8484
for (;;) {
8585
const okl_long r = okl::sys(okl::nr_sendto, fd,
8686
reinterpret_cast<okl_long>(buf),
8787
static_cast<okl_long>(len), 0,
8888
reinterpret_cast<okl_long>(&ss), addrlen);
8989
if (okl::interrupted(r)) continue;
90-
if (okl::failed(r)) return { 0, okl::translate(r) };
90+
if (okl::failed(r)) return -okl::translate(r);
9191

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

103-
kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
104-
kal_endpoint* from) {
103+
kal_intptr kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
104+
kal_endpoint* from) {
105105
const int fd = fd_of(d);
106-
if (fd < 0) return { 0, kal_err_invalid };
106+
if (fd < 0) return -kal_err_invalid;
107107

108108
okl::ksockaddr_storage ss{};
109109
okl_long addrlen = static_cast<okl_long>(sizeof ss);
@@ -115,7 +115,7 @@ kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
115115
reinterpret_cast<okl_long>(&ss),
116116
reinterpret_cast<okl_long>(&addrlen));
117117
if (okl::interrupted(r)) continue;
118-
if (okl::failed(r)) return { 0, okl::translate(r) };
118+
if (okl::failed(r)) return -okl::translate(r);
119119

120120
// THE COUNT REPORTED IS WHAT WAS PLACED IN THE BUFFER, not what was
121121
// sent. Without MSG_TRUNC the kernel already reports the former, which
@@ -131,7 +131,7 @@ kal_io_result kal_datagram_recv_from(kal_datagram d, void* buf, kal_uintptr len,
131131
from->port = 0;
132132
}
133133
}
134-
return { static_cast<kal_uintptr>(r), kal_ok };
134+
return static_cast<kal_intptr>(r);
135135
}
136136
}
137137

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

151151
} // extern "C"

‎src/env.cpp‎

Lines changed: 103 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,55 +34,140 @@ okl_ulong auxval(okl_ulong key) {
3434
} // namespace okl
3535

3636
namespace {
37+
38+
// ⚠️⚠️ ONE C LIBRARY PASSES THESE AND ANOTHER DOES NOT, AND THE ONE THAT DOES
39+
// NOT IS THE ONE THIS PACKAGE EXISTS TO SIT BENEATH.
40+
//
41+
// glibc calls every `.init_array' entry with (argc, argv, envp). musl calls
42+
// them with NO ARGUMENTS. A function declared to take three therefore receives
43+
// whatever the argument registers happened to hold, and this one recorded it.
44+
//
45+
// ⭐ MEASURED 2026-08-29, WITH THE CONTROL THAT SEPARATES THE TWO EXPLANATIONS.
46+
// It was found by running the tests for aarch64, where the first enquiry after
47+
// the count faulted --- which reads as an architecture defect. It is not:
48+
//
49+
// target argc argv envp
50+
// x86_64-linux-gnu 1 <stack> <stack>
51+
// x86_64-linux-musl 0x4004c2 1 <stack>
52+
// aarch64-linux-musl 0x405ee4 1 <stack>
53+
//
54+
// Both musl rows are shifted by one and the glibc row is not, so the axis is
55+
// the C library. Running only aarch64 would have attributed it to the machine.
56+
//
57+
// So the arguments are CHECKED rather than believed, and where they do not hold
58+
// the vectors are recovered from the one handle both libraries publish.
59+
bool plausible(int argc, char* const* argv, char* const* envp) {
60+
if (argc < 0 || argc > 65536) return false;
61+
if (argv == nullptr) return false;
62+
if (envp == nullptr) return false;
63+
if (argv[argc] != nullptr) return false; // argv is terminated at argc
64+
if (argc > 0 && argv[0] == nullptr) return false;
65+
return true;
66+
}
67+
68+
// ⚠️ WEAK, AND DATA RATHER THAN A CALL. The independence check in this package
69+
// forbids reaching for the C library's names, because a CALL into the runtime a
70+
// program supplied would resolve to the program's and could re-enter this
71+
// implementation without bound. A pointer cannot: it is read once, it executes
72+
// nothing, and being weak it is null in a program that has no C library --- in
73+
// which case this implementation supplied `_start' and recorded the vectors
74+
// there, and this path is not taken.
75+
extern "C" char** environ __attribute__((weak));
76+
77+
// The initial stack, whose shape is the ELF ABI's rather than any library's:
78+
//
79+
// argc argv[0] .. argv[argc-1] NULL envp[0] .. NULL auxv...
80+
//
81+
// so from `envp' the argument vector is reached by walking back over its
82+
// terminator. The walk is CHECKED and not trusted: the count found in the slot
83+
// below argv[0] must equal the number of entries actually there, which a run of
84+
// unrelated stack words does not satisfy. Where it does not hold, nothing is
85+
// recorded and the program is told it has no arguments -- which is an answer,
86+
// and is what clause 7.7 asks of an implementation that cannot know.
87+
bool recover(char*** argv_out, int* argc_out, char*** envp_out) {
88+
char** e = environ;
89+
if (e == nullptr || e[-1] != nullptr) return false;
90+
for (long k = 0; k <= 65536; ++k) {
91+
auto* slot = reinterpret_cast<long*>(e - 2 - k);
92+
if (*slot != k) continue;
93+
char** candidate = e - 1 - k;
94+
bool holds = true;
95+
for (long i = 0; i < k && holds; ++i) if (candidate[i] == nullptr) holds = false;
96+
if (!holds || candidate[k] != nullptr) continue;
97+
*argv_out = candidate; *argc_out = static_cast<int>(k); *envp_out = e;
98+
return true;
99+
}
100+
return false;
101+
}
102+
37103
[[gnu::constructor(101)]] void capture(int argc, char** argv, char** envp) {
38-
if (okl::g_argv == nullptr) okl::record(argc, argv, envp);
104+
if (okl::g_argv != nullptr) return;
105+
if (plausible(argc, argv, envp)) { okl::record(argc, argv, envp); return; }
106+
char** rargv = nullptr; char** renvp = nullptr; int rargc = 0;
107+
if (recover(&rargv, &rargc, &renvp)) okl::record(rargc, rargv, renvp);
39108
}
40109
} // namespace
41110

42111
extern "C" {
43112

113+
// EVERY VALUE IS COPIED INTO THE CALLER'S BUFFER. These answered with a pointer
114+
// into this implementation's own storage, which is meaningful only while the
115+
// implementation shares the caller's address space --- so the interface said
116+
// something different depending on how it was reached, which is the one thing a
117+
// contract must not do (clause 4.4).
118+
//
119+
// Each returns the length the value HAS, so a caller with a large enough buffer
120+
// is done in one call, a caller that wants to size first passes a capacity of
121+
// zero, and a caller whose buffer was too small learns it by comparing.
122+
namespace {
123+
kal_intptr give(const char* v, kal_uintptr n, char* out, kal_uintptr cap) {
124+
if (out != nullptr && cap != 0) okl::copy(out, v, n < cap ? n : cap);
125+
return static_cast<kal_intptr>(n);
126+
}
127+
} // namespace
128+
44129
kal_uintptr kal_env_arg_count(void) { return static_cast<kal_uintptr>(okl::g_argc); }
45130

46-
const char* kal_env_arg(kal_uintptr index, kal_uintptr* len) {
47-
if (index >= static_cast<kal_uintptr>(okl::g_argc)) { if (len) *len = 0; return nullptr; }
131+
kal_intptr kal_env_arg(kal_uintptr index, char* out, kal_uintptr cap) {
132+
if (index >= static_cast<kal_uintptr>(okl::g_argc)) return -kal_err_not_found;
48133
const char* s = okl::g_argv[index];
49-
if (len) *len = okl::length(s);
50-
return s;
134+
return give(s, okl::length(s), out, cap);
51135
}
52136

53-
const char* kal_env_var(const char* name, kal_uintptr name_len, kal_uintptr* value_len) {
137+
kal_intptr kal_env_var(const char* name, kal_uintptr name_len,
138+
char* out, kal_uintptr cap) {
139+
if (name == nullptr) return -kal_err_invalid;
54140
for (char** e = okl::g_envp; e && *e; ++e) {
55141
const char* entry = *e;
56142
kal_uintptr i = 0;
57143
while (i < name_len && entry[i] != '\0' && entry[i] == name[i]) ++i;
58144
if (i == name_len && entry[i] == '=') {
59145
const char* v = entry + name_len + 1;
60-
if (value_len) *value_len = okl::length(v);
61-
return v;
146+
return give(v, okl::length(v), out, cap);
62147
}
63148
}
64-
if (value_len) *value_len = 0;
65-
return nullptr;
149+
// A name that is not there is distinct from one whose value is empty, and
150+
// reporting a length of zero for both would lose that.
151+
return -kal_err_not_found;
66152
}
67153

68154
kal_uintptr kal_env_var_count(void) {
69155
kal_uintptr n = 0; for (char** e = okl::g_envp; e && *e; ++e) ++n; return n;
70156
}
71157

72-
const char* kal_env_var_at(kal_uintptr index, kal_uintptr* name_len,
73-
const char** value, kal_uintptr* value_len) {
158+
// The NAME at a position. The value is then obtained by kal_env_var: an
159+
// operation answering both needs two buffers, two capacities and two lengths,
160+
// and its second half is kal_env_var written again. The set does not change
161+
// while the program runs, so an index may be held across the two calls.
162+
kal_intptr kal_env_var_at(kal_uintptr index, char* out, kal_uintptr cap) {
74163
kal_uintptr n = 0;
75164
for (char** e = okl::g_envp; e && *e; ++e, ++n) {
76165
if (n != index) continue;
77166
const char* entry = *e;
78167
kal_uintptr i = 0; while (entry[i] != '\0' && entry[i] != '=') ++i;
79-
if (name_len) *name_len = i;
80-
const char* v = entry[i] == '=' ? entry + i + 1 : entry + i;
81-
if (value) *value = v;
82-
if (value_len) *value_len = okl::length(v);
83-
return entry;
168+
return give(entry, i, out, cap);
84169
}
85-
return nullptr;
170+
return -kal_err_not_found;
86171
}
87172

88173
}

‎src/exec.cpp‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ void kal_exec_free(void* p, kal_uintptr size) {
9595
// A published region may be reserved for writing again: this kernel's
9696
// protection call is not one-way. The position is set accordingly, and a
9797
// caller that must change published bytes need not abandon the region.
98-
const kal_uintptr kal_exec_props = KAL_EXEC_PROP_REPUBLISH;
98+
kal_uintptr kal_exec_props(void) {
99+
// Executable memory is available to every artifact on this kernel: nothing
100+
// here is granted only to a program produced in a particular way.
101+
return KAL_EXEC_PROP_REPUBLISH | KAL_EXEC_PROP_AVAILABLE;
102+
}
99103

100104
} // extern "C"

0 commit comments

Comments
 (0)