Skip to content
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ written on the kernel's own calls.

```toml
[dependencies]
openkal = "0.16.0"
openkal = "0.17.0"

[target.'cfg(os = "macos")'.dependencies]
openkal-macos = "0.14.1"
openkal-macos = "0.15.0"
```

Its purpose is as much to test the specification as to be used. A specification
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-macos"
version = "0.14.1"
version = "0.15.0"
description = "An implementation of openkal for macOS, written on the kernel's own calls. Its purpose is as much to test the specification as to be used."
license = "Apache-2.0"

Expand Down Expand Up @@ -57,7 +57,7 @@ provides-interfaces = [
]

[dependencies]
openkal = "0.16.0"
openkal = "0.17.0"

[build]
# The flags are attached to this package's own sources rather than to the whole
Expand Down
4 changes: 4 additions & 0 deletions src/endpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ inline int from_kernel(const ksockaddr_storage& in, kal_endpoint& out) {
return kal_ok;
}

// A connection made at a name (0.17) has no address and port to state; the
// kernel reports the local family, or an unnamed one for an accepted end.
if (family == 1 /* AF_UNIX */ || family == 0) return kal_err_not_supported;

return kal_err_invalid;
}

Expand Down
72 changes: 66 additions & 6 deletions src/env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ namespace okm {
int g_argc = 0;
char** g_argv = nullptr;
char** g_envp = nullptr;
// Whether SIGPIPE was ignored HERE, at the default this program was started
// with: a program it starts is given the default back (process.cpp).
bool g_pipe_ignored_here = false;

void record(int argc, char** argv, char** envp) {
g_argc = argc; g_argv = argv; g_envp = envp;
Expand Down Expand Up @@ -41,14 +44,33 @@ namespace {
// mistake that shows up as a program dying in a way nobody can trace --- which is
// the defect this note is about, arrived at from the other side.
//
// ⇒ It is left until it can be MEASURED on this system. This repository already
// refuses to claim a facility it has not exercised, and a signal disposition
// installed by guesswork is exactly that. The consequence meanwhile is stated:
// a program above this implementation that writes to a stream whose far end has
// gone is ended by SIGPIPE rather than told, and no layer between it and here
// can name what happened.
// ⇒ MEASURED SINCE (0.15): the stop word installs a handler through this
// kernel's `sigaction' with this implementation's own trampoline, and the
// conformance suite raises the signal and returns from it. Ignoring needs no
// trampoline at all --- the kernel never enters one for an ignored signal ---
// so the shape is the measured one and the trampoline field is left empty.
//
// ONLY WHERE NOTHING ELSE HAS CHOSEN. A program whose own runtime installed a
// handler for SIGPIPE keeps it; a disposition still at its default is the one
// that ends the program without telling it, and that is the one replaced.
//
// AND IT IS THIS PROGRAM'S, NOT PASSED ON. An ignored disposition survives
// `execve', so a program started from here would begin with SIGPIPE ignored and
// a shell it runs could not undo it. `kal_process_spawn' gives the default back
// to the program it starts when the ignore was made here (process.cpp).
void ignore_sigpipe() {
struct { void (*handler)(int); void* tramp; unsigned int mask; int flags; } act{};
struct { void (*handler)(int); unsigned int mask; int flags; } old{};
if (okm::failed(okm::sys(46 /* sigaction */, 13 /* SIGPIPE */, 0, reinterpret_cast<okm_long>(&old))))
return;
if (old.handler != nullptr) return; // not SIG_DFL
act.handler = reinterpret_cast<void (*)(int)>(1); // SIG_IGN
if (!okm::failed(okm::sys(46 /* sigaction */, 13, reinterpret_cast<okm_long>(&act), 0)))
okm::g_pipe_ignored_here = true;
}
[[gnu::constructor(101)]] void capture(int argc, char** argv, char** envp) {
if (okm::g_argv == nullptr) okm::record(argc, argv, envp);
ignore_sigpipe();
}
} // namespace

Expand Down Expand Up @@ -108,4 +130,42 @@ kal_intptr kal_env_var_at(kal_uintptr index, char* out, kal_uintptr cap) {
return -kal_err_not_found;
}

// THE IMAGE THE KERNEL STARTED (0.17): what `proc_pidpath' answers, the
// kernel's record of the file it mapped (`proc_info', PROC_PIDPATHINFO);
// where that is refused, `executable_path=' from the vector this kernel passes
// after the environment, which is the name the kernel was given.
// A program built for its environment was loaded by nothing the interface
// names (clause 12: only an image has a loader to name).
kal_intptr kal_env_loader(char* out, kal_uintptr cap) {
(void)out; (void)cap;
return -kal_err_not_found;
}

kal_intptr kal_env_image(char* out, kal_uintptr cap) {
char buf[4096];
const okm_long n = okm::sys(okm::nr_proc_info, 2 /* PROC_INFO_CALL_PIDINFO */,
okm::sys(okm::nr_getpid), 11 /* PROC_PIDPATHINFO */, 0,
reinterpret_cast<okm_long>(buf), sizeof buf);
if (!okm::failed(n) && n > 0) {
kal_uintptr len = 0; while (len < sizeof buf && buf[len] != '\0') ++len;
if (len > 0) return give(buf, len, out, cap);
}
if (okm::g_envp != nullptr) {
char** a = okm::g_envp;
while (*a) ++a;
for (++a; *a; ++a) {
const char key[] = "executable_path=";
kal_uintptr i = 0;
while (key[i] != '\0' && (*a)[i] == key[i]) ++i;
if (key[i] == '\0') return give(*a + i, okm::length(*a + i), out, cap);
}
}
return -kal_err_not_found;
}

// For conventions only, as the declaration restricts it.
kal_intptr kal_env_system(char* out, kal_uintptr cap) {
return give("macos", 5, out, cap);
}

}
16 changes: 16 additions & 0 deletions src/exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,22 @@ int kal_exec_publish(void* p, kal_uintptr size) {
return kal_ok;
}

// PART OF A REGION (0.17): a run of whole pages made executable, the rest left
// writable. A part that is not whole pages is refused rather than rounded.
int kal_exec_publish_part(void* region, kal_uintptr offset, kal_uintptr size) {
const okm_uptr g = granularity();
if (region == nullptr || size == 0 || (offset & (g - 1)) != 0 || (size & (g - 1)) != 0
|| (reinterpret_cast<okm_uptr>(region) & (g - 1)) != 0)
return kal_err_invalid;
const okm_long r = okm::sys(okm::nr_mprotect,
reinterpret_cast<okm_long>(static_cast<char*>(region) + offset),
static_cast<okm_long>(size), okm::prot_read | okm::prot_exec);
return okm::failed(r) ? okm::translate(r) : kal_ok;
}

// The page this kernel protects in, which is also what it reserves in.
kal_uintptr kal_exec_granularity(void) { return granularity(); }

void kal_exec_free(void* p, kal_uintptr size) {
if (p == nullptr || size == 0) return;
const okm_uptr bytes = round_up(static_cast<okm_uptr>(size), granularity());
Expand Down
44 changes: 41 additions & 3 deletions src/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ kal_uintptr volume_props(int fd) {
const kal_uintptr conservative =
KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_ATOMIC_RENAME
| KAL_FS_PROP_LOCKS | KAL_FS_PROP_CAPACITY;
// A NAME HELD OPEN IS REMOVED AT ONCE (0.17, clause 7.14) on the formats
// whose names this kernel keeps: `unlink' drops the entry and the file
// lives on until its last descriptor closes. A network volume's client
// renames a held file to a hidden name instead, so the directory that held
// it is not empty until the close, and the position is not claimed there.
const kal_uintptr local = conservative | KAL_FS_PROP_REMOVE_OPEN;
if (fd < 0) return 0;

okm::kstatfs64 sf{};
Expand All @@ -162,9 +168,13 @@ kal_uintptr volume_props(int fd) {

// Nodes whose content is another name, on a format that stores a mode.
// This is the ordinary volume of this system.
//
// Both implement `renameatx_np' with RENAME_EXCL (0.17), and both store a
// socket bound to a name.
if (named("apfs") || named("hfs"))
return conservative | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS
| KAL_FS_PROP_EXECUTABLE;
return local | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS
| KAL_FS_PROP_EXECUTABLE
| KAL_FS_PROP_RENAME_NO_REPLACE | KAL_FS_PROP_LOCAL_ENDPOINT;

// The automounter's own namespace: links, and no mode of its own.
if (named("autofs"))
Expand All @@ -179,7 +189,7 @@ kal_uintptr volume_props(int fd) {
// The FAT family stores neither a case distinction nor a node that names
// another, nor a mode. `symlinkat' on such a volume reports a refusal, and
// this is where a caller learns it before it tries.
if (named("msdos") || named("exfat")) return conservative;
if (named("msdos") || named("exfat")) return local;

return conservative;
}
Expand Down Expand Up @@ -659,6 +669,25 @@ int kal_fs_rename(kal_dir from, const char* a, kal_uintptr alen,
return okm::failed(r) ? okm::translate(r) : kal_ok;
}

// A RENAME THAT DOES NOT REPLACE (0.17): `renameatx_np' with RENAME_EXCL, the
// kernel's own test-and-rename. A format that does not implement the flag
// answers ENOTSUP, which is a volume that cannot.
int kal_fs_rename_flags(kal_dir from, const char* a, kal_uintptr alen,
kal_dir to, const char* b, kal_uintptr blen, kal_uintptr flags) {
if ((flags & ~static_cast<kal_uintptr>(KAL_RENAME_NO_REPLACE)) != 0) return kal_err_invalid;
if (flags == 0) return kal_fs_rename(from, a, alen, to, b, blen);
const int f = okm::unpack(from.h), t2 = okm::unpack(to.h);
if (f < 0 || t2 < 0 || !okm::acceptable(a, alen) || !okm::acceptable(b, blen))
return kal_err_invalid;
if ((volume_props(t2) & KAL_FS_PROP_RENAME_NO_REPLACE) == 0) return kal_err_not_supported;
okm::terminated ta(a, alen), tb(b, blen);
if (!ta.ok || !tb.ok) return kal_err_invalid;
const okm_long r = okm::sys(okm::nr_renameatx_np, f, reinterpret_cast<okm_long>(ta.buf),
t2, reinterpret_cast<okm_long>(tb.buf), 0x4 /* RENAME_EXCL */);
if (r == -45 /* ENOTSUP */) return kal_err_not_supported;
return okm::failed(r) ? okm::translate(r) : kal_ok;
}

int kal_fs_list_begin(kal_dir d, kal_uintptr* iter) {
const int fd = okm::unpack(d.h);
if (fd < 0 || iter == nullptr) return kal_err_invalid;
Expand Down Expand Up @@ -720,6 +749,15 @@ int kal_fs_list_next(kal_dir d, kal_uintptr* iter,
// the position reports it in advance, which no operation could.
kal_uintptr kal_fs_props(kal_dir d) { return volume_props(okm::unpack(d.h)); }

} // extern "C" (reopened below)

namespace okm {
// For openkal.net's rendezvous at a name.
kal_uintptr volume_props_of(int fd) { return volume_props(fd); }
} // namespace okm

extern "C" {

// Nodes whose content is another name.
int kal_fs_link_create(kal_dir base, const char* name, kal_uintptr len,
const char* target, kal_uintptr target_len,
Expand Down
56 changes: 56 additions & 0 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,64 @@ int report_address(okm_long call, int fd, kal_endpoint* out) {

} // namespace

namespace okm { kal_uintptr volume_props_of(int fd); } // fs.cpp

namespace {
// A RENDEZVOUS AT A NAME (0.17). This kernel binds a local socket to a path and
// has no call that binds beneath a descriptor; it does have a working
// directory per THREAD (`__pthread_fchdir'), so the name is bound relative to
// the directory the caller holds by entering it on this thread alone and
// leaving it again --- no other context's names change meaning meanwhile. The
// address holds 104 bytes.
int at_name(kal_dir base, const char* name, kal_uintptr len, bool listen, kal_uintptr& out) {
const int dir = okm::unpack(base.h);
if (dir < 0 || len == 0 || len > 103 || !okm::acceptable(name, len)) return kal_err_invalid;
for (kal_uintptr i = 0; i < len; ++i) if (name[i] == '/') return kal_err_invalid;
if ((okm::volume_props_of(dir) & KAL_FS_PROP_LOCAL_ENDPOINT) == 0) return kal_err_not_supported;
unsigned char addr[106] = {};
addr[0] = static_cast<unsigned char>(2 + len + 1); // sun_len
addr[1] = 1; // AF_UNIX
for (kal_uintptr i = 0; i < len; ++i) addr[2 + i] = static_cast<unsigned char>(name[i]);
const okm_long fd = okm::sys(okm::nr_socket, 1 /* AF_UNIX */, okm::sock_stream, 0);
if (okm::failed(fd)) return okm::translate(fd);
close_on_exec(fd);
if (const okm_long r = okm::sys(okm::nr_pthread_fchdir, dir); okm::failed(r)) {
okm::sys(okm::nr_close, fd);
return okm::translate(r);
}
okm_long r = 0;
if (listen) {
r = okm::sys(okm::nr_bind, fd, reinterpret_cast<okm_long>(addr), addr[0]);
} else {
do r = okm::sys(okm::nr_connect, fd, reinterpret_cast<okm_long>(addr), addr[0]);
while (okm::interrupted(r));
}
okm::sys(okm::nr_pthread_fchdir, -1); // back to the process's directory
if (!okm::failed(r) && listen) r = okm::sys(okm::nr_listen, fd, 128);
if (okm::failed(r)) {
okm::sys(okm::nr_close, fd);
if (listen && r == -48 /* EADDRINUSE */) return kal_err_exists;
if (!listen && (r == -2 /* ENOENT */ || r == -61 /* ECONNREFUSED */)) return kal_err_not_found;
return okm::translate(r);
}
out = okm::pack(static_cast<int>(fd));
if (out == 0) { okm::sys(okm::nr_close, fd); return kal_err_no_memory; }
return kal_ok;
}
} // namespace

extern "C" {

int kal_net_listen_at(kal_dir base, const char* name, kal_uintptr len, kal_net_listener* out) {
if (out == nullptr) return kal_err_invalid;
return at_name(base, name, len, true, out->h);
}

int kal_net_connect_at(kal_dir base, const char* name, kal_uintptr len, kal_net_conn* out) {
if (out == nullptr) return kal_err_invalid;
return at_name(base, name, len, false, out->h);
}

int kal_net_connect(const kal_endpoint* to, kal_net_conn* out) {
if (to == nullptr || out == nullptr) return kal_err_invalid;
const okm_long family = okm::family_of(*to);
Expand Down
Loading
Loading