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

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

[target.'cfg(os = "linux")'.dependencies]
openkal-linux = "0.17.0"
openkal-linux = "0.18.0"
```

## Why it does not use a C library
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-linux"
version = "0.17.0"
version = "0.18.0"
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."
license = "Apache-2.0"

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

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

# The package contributes definitions and no modules. The interface it
# implements is declared by the specification package, which this package
Expand Down
3 changes: 3 additions & 0 deletions src/endpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ inline int from_kernel(const ksockaddr_storage& in, kal_endpoint& out) {
return kal_ok;
}

// A connection made at a name (0.17): it has no address and port to state.
if (in.family == 1 /* AF_UNIX */) return kal_err_not_supported;

return kal_err_invalid;
}

Expand Down
62 changes: 56 additions & 6 deletions src/env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ namespace okl {
int g_argc = 0;
char** g_argv = nullptr;
char** g_envp = nullptr;
// Whether this program was started with SIGPIPE ignored: what a program it
// starts is given back (process.cpp).
bool g_pipe_started_ignored = false;
okl_ulong* g_auxv = nullptr;

void record(int argc, char** argv, char** envp) {
Expand Down Expand Up @@ -124,19 +127,35 @@ bool recover(char*** argv_out, int* argc_out, char*** envp_out) {
// NOT A POLICY CHOICE ABOUT SIGNALS IN GENERAL. This is the one signal an
// ordinary openkal operation provokes; the rest are left exactly as this program
// was started with.
//
// AND IT IS THIS PROGRAM'S, NOT PASSED ON. An ignored disposition survives
// `execve', so every program started from here would otherwise begin with
// SIGPIPE ignored --- `yes | head' inside a started shell then reports a write
// error instead of ending, and a shell cannot undo an ignore it was started
// with. What was found here is recorded, and `kal_process_start' gives a
// started program the disposition this one was started with (process.cpp).
[[gnu::constructor(101)]] void quiet_the_signal_openkal_cannot_report() {
// struct k_sigaction as this kernel takes it: handler, flags, restorer, mask.
struct { void* handler; unsigned long flags; void* restorer; unsigned long mask; }
ignore{ reinterpret_cast<void*>(1) /* SIG_IGN */, 0, nullptr, 0 };
okl::sys(okl::nr_rt_sigaction, 13 /* SIGPIPE */,
reinterpret_cast<okl_long>(&ignore), 0, sizeof ignore.mask);
struct k_sigaction { void* handler; unsigned long flags; void* restorer; unsigned long mask; };
k_sigaction ignore{ reinterpret_cast<void*>(1) /* SIG_IGN */, 0, nullptr, 0 };
k_sigaction found{};
if (!okl::failed(okl::sys(okl::nr_rt_sigaction, 13 /* SIGPIPE */,
reinterpret_cast<okl_long>(&ignore),
reinterpret_cast<okl_long>(&found), sizeof ignore.mask)))
okl::g_pipe_started_ignored = found.handler == reinterpret_cast<void*>(1);
}

// THE PUBLISHED HANDLE FIRST, THE REGISTERS ONLY WHERE THERE IS NONE. The
// check above tells a shifted vector from a real one, not every set of
// register contents from one: a static program over musl (an ok image's M3
// program) passed it with whatever the registers held, and its image saw no
// environment at all. `environ' is the C library's own record of the vectors
// it was started with, so it is read wherever the library has one.
[[gnu::constructor(101)]] void capture(int argc, char** argv, char** envp) {
if (okl::g_argv != nullptr) return;
if (plausible(argc, argv, envp)) { okl::record(argc, argv, envp); return; }
char** rargv = nullptr; char** renvp = nullptr; int rargc = 0;
if (recover(&rargv, &rargc, &renvp)) okl::record(rargc, rargv, renvp);
if (recover(&rargv, &rargc, &renvp)) { okl::record(rargc, rargv, renvp); return; }
if (plausible(argc, argv, envp)) okl::record(argc, argv, envp);
}
} // namespace

Expand Down Expand Up @@ -202,4 +221,35 @@ 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). `/proc/self/exe' names the file the
// kernel mapped, resolved; where the pseudo-file system is not mounted, the
// name the kernel was given (AT_EXECFN), which is the starter's spelling and
// may be relative to where the starter was --- the environment's answer
// either way, which is what the declaration asks for.
// 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 okl_long n = okl::sys(okl::nr_readlinkat, okl::at_fdcwd,
reinterpret_cast<okl_long>("/proc/self/exe"),
reinterpret_cast<okl_long>(buf), sizeof buf);
if (!okl::failed(n) && n > 0 && n < static_cast<okl_long>(sizeof buf))
return give(buf, static_cast<kal_uintptr>(n), out, cap);
if (const okl_ulong fn = okl::auxval(31 /* AT_EXECFN */); fn != 0) {
const char* name = reinterpret_cast<const char*>(fn);
return give(name, okl::length(name), 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("linux", 5, out, cap);
}

}
37 changes: 33 additions & 4 deletions src/exec.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "sys.h"
#include <openkal/exec.h>

namespace okl { okl_ulong auxval(okl_ulong key); }

// openkal.exec on this kernel.
//
// A mapping obtained writable and made executable afterwards. The kernel places
Expand All @@ -18,7 +20,18 @@

namespace {

constexpr okl_uptr kPage = 4096;
// The kernel's page, as it told this program at inception (AT_PAGESZ). Four
// kibibytes was written here, and an aarch64 kernel built for sixteen or
// sixty-four rounds every protection to its own page, so a part published at a
// four-kibibyte boundary would have taken its neighbour with it.
okl_uptr page() {
static okl_uptr p = 0;
if (p == 0) {
const okl_ulong a = okl::auxval(6 /* AT_PAGESZ */);
p = (a != 0 && (a & (a - 1)) == 0) ? static_cast<okl_uptr>(a) : 4096;
}
return p;
}

okl_uptr round_up(okl_uptr n, okl_uptr to) { return (n + to - 1) & ~(to - 1); }

Expand All @@ -28,7 +41,7 @@ extern "C" {

void* kal_exec_alloc(kal_uintptr size) {
if (size == 0) return nullptr;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), kPage);
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
const okl_long r = okl::sys(okl::nr_mmap, 0, static_cast<okl_long>(bytes),
okl::prot_read | okl::prot_write,
okl::map_private | okl::map_anonymous, -1, 0);
Expand All @@ -38,7 +51,7 @@ void* kal_exec_alloc(kal_uintptr size) {

int kal_exec_publish(void* p, kal_uintptr size) {
if (p == nullptr || size == 0) return kal_err_invalid;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), kPage);
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
const okl_long r = okl::sys(okl::nr_mprotect, reinterpret_cast<okl_long>(p),
static_cast<okl_long>(bytes),
okl::prot_read | okl::prot_exec);
Expand Down Expand Up @@ -85,9 +98,25 @@ int kal_exec_publish(void* p, kal_uintptr size) {
return kal_ok;
}

// PART OF A REGION (0.17): the kernel protects per page, so a part is a run of
// whole pages, and a part that is not is refused rather than rounded --- a
// rounded part would take writable data beside it out of reach.
int kal_exec_publish_part(void* region, kal_uintptr offset, kal_uintptr size) {
const okl_uptr g = page();
if (region == nullptr || size == 0 || (offset & (g - 1)) != 0 || (size & (g - 1)) != 0
|| (reinterpret_cast<okl_uptr>(region) & (g - 1)) != 0)
return kal_err_invalid;
const okl_long r = okl::sys(okl::nr_mprotect,
reinterpret_cast<okl_long>(static_cast<char*>(region) + offset),
static_cast<okl_long>(size), okl::prot_read | okl::prot_exec);
return okl::failed(r) ? okl::translate(r) : kal_ok;
}

kal_uintptr kal_exec_granularity(void) { return page(); }

void kal_exec_free(void* p, kal_uintptr size) {
if (p == nullptr || size == 0) return;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), kPage);
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
okl::sys(okl::nr_munmap, reinterpret_cast<okl_long>(p),
static_cast<okl_long>(bytes));
}
Expand Down
61 changes: 55 additions & 6 deletions src/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ 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 every format
// whose names the VFS itself keeps: `unlink' drops the entry and the inode
// lives on until its last descriptor closes. Not in the conservative set,
// because a format the VFS does not keep may not: a network volume's client
// renames a held file to a hidden name and removes it at the close, and a
// user-space format may do the same, so the directory is not empty.
const kal_uintptr local = conservative | KAL_FS_PROP_REMOVE_OPEN;
if (fd < 0) return 0;

okl::kstatfs sf{};
Expand All @@ -143,12 +150,23 @@ kal_uintptr volume_props(int fd) {

switch (sf.f_type) {
// Formats with a case-sensitive namespace and nodes that name others.
//
// A rename that does not replace (0.17) is `renameat2' with
// RENAME_NOREPLACE, which each of these formats implements except the
// one whose support arrived late and is not known from its type; a
// socket bound to a name (0.17, KAL_FS_PROP_LOCAL_ENDPOINT) is a node
// every one of them stores.
case okl::fs_ext234: case okl::fs_btrfs: case okl::fs_xfs:
case okl::fs_f2fs: case okl::fs_tmpfs: case okl::fs_overlay:
case okl::fs_zfs: case okl::fs_bcachefs:
return conservative | KAL_FS_PROP_CASE_SENSITIVE
case okl::fs_bcachefs:
return local | KAL_FS_PROP_CASE_SENSITIVE
| KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS
| KAL_FS_PROP_EXECUTABLE;
| KAL_FS_PROP_EXECUTABLE
| KAL_FS_PROP_RENAME_NO_REPLACE | KAL_FS_PROP_LOCAL_ENDPOINT;
case okl::fs_zfs:
return local | KAL_FS_PROP_CASE_SENSITIVE
| KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS
| KAL_FS_PROP_EXECUTABLE | KAL_FS_PROP_LOCAL_ENDPOINT;

// Read-only formats: the nodes are there and none can be made, and a
// rename cannot be atomic because there is no rename.
Expand All @@ -164,21 +182,31 @@ kal_uintptr volume_props(int fd) {
// names another. `symlink' on such a volume reports EPERM, and this is
// where a caller learns that before it tries.
case okl::fs_msdos: case okl::fs_exfat:
return conservative;
return local;

// A case-insensitive namespace, with nodes that name others.
case okl::fs_ntfs: case okl::fs_ntfs3:
return conservative | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS;
return local | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS;
// The same, on a format that stores a mode.
case okl::fs_hfsplus:
return conservative | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS
return local | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS
| KAL_FS_PROP_EXECUTABLE;

default:
return conservative;
}
}

} // namespace (reopened below)

namespace okl {
// The volume's properties, for the interfaces outside this file that ask one
// (openkal.net's rendezvous at a name).
kal_uintptr volume_props_of(int fd) { return volume_props(fd); }
} // namespace okl

namespace {

// Writes no more of the structure than the caller says exists on its side, and
// reports which fields it filled.
//
Expand Down Expand Up @@ -594,6 +622,27 @@ int kal_fs_rename(kal_dir from, const char* a, kal_uintptr alen,
return okl::failed(r) ? okl::translate(r) : kal_ok;
}

// A RENAME THAT DOES NOT REPLACE (0.17) is the kernel's own: `renameat2' with
// RENAME_NOREPLACE tests and renames in one act. A format that does not
// implement the flag answers EINVAL, and a kernel before 3.15 ENOSYS; both are
// a volume that cannot, which `kal_fs_props' answers before the call for the
// formats this file knows.
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 = okl::unpack(from.h), t2 = okl::unpack(to.h);
if (f < 0 || t2 < 0 || !okl::acceptable(a, alen) || !okl::acceptable(b, blen)) return kal_err_invalid;
if ((volume_props(t2) & KAL_FS_PROP_RENAME_NO_REPLACE) == 0) return kal_err_not_supported;
okl::terminated ta(a, alen), tb(b, blen);
if (!ta.ok || !tb.ok) return kal_err_invalid;
const okl_long r = okl::sys(okl::nr_renameat2, f, reinterpret_cast<okl_long>(ta.buf),
t2, reinterpret_cast<okl_long>(tb.buf), 1 /* RENAME_NOREPLACE */);
if (r == -okl::e_inval || r == -okl::e_nosys) return kal_err_not_supported;
return okl::failed(r) ? okl::translate(r) : kal_ok;
}

// Enumeration holds a descriptor of its own, obtained by opening the directory
// through itself. A duplicate would share the file offset with the handle the
// caller holds, so two enumerations of one directory would consume each
Expand Down
75 changes: 75 additions & 0 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "endpoint.h"
#include <openkal/net.h>

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

// openkal.net upon the kernel's socket calls.
//
// A CONNECTION IS AN OWNED HANDLE AND THE STREAM IS BORROWED FROM IT, exactly as
Expand Down Expand Up @@ -127,6 +129,79 @@ int kal_net_accept(kal_net_listener l, kal_net_conn* out) {
}
}

// A RENDEZVOUS AT A NAME (0.17): a socket of the local family bound to a name
// in the directory. The kernel binds to a path and not beneath a descriptor,
// so the directory is named through the descriptor itself,
// `/proc/self/fd/<dir>/<name>', which the kernel resolves through the
// descriptor --- the directory the caller holds, wherever it has since been
// moved. The address holds 108 bytes, and a name that does not fit beside
// that prefix is one this environment cannot place (kal_err_invalid).
static int local_address(kal_dir base, const char* name, kal_uintptr len,
unsigned char (&addr)[110], okl_long& alen) {
const int dir = okl::unpack(base.h);
if (dir < 0 || !okl::acceptable(name, len) || len == 0) return kal_err_invalid;
for (kal_uintptr i = 0; i < len; ++i) if (name[i] == '/') return kal_err_invalid;
if ((okl::volume_props_of(dir) & KAL_FS_PROP_LOCAL_ENDPOINT) == 0) return kal_err_not_supported;
char path[108];
const char prefix[] = "/proc/self/fd/";
okl_uptr at = 0;
for (okl_uptr i = 0; i + 1 < sizeof prefix; ++i) path[at++] = prefix[i];
char d[12]; int n = 0;
for (unsigned v = static_cast<unsigned>(dir); ; v /= 10) { d[n++] = static_cast<char>('0' + v % 10); if (v < 10) break; }
while (n) path[at++] = d[--n];
path[at++] = '/';
if (at + len + 1 > sizeof path) return kal_err_invalid;
for (kal_uintptr i = 0; i < len; ++i) path[at++] = name[i];
path[at] = '\0';
addr[0] = 1; addr[1] = 0; // sa_family = AF_UNIX
for (okl_uptr i = 0; i <= at; ++i) addr[2 + i] = static_cast<unsigned char>(path[i]);
alen = static_cast<okl_long>(2 + at + 1);
return kal_ok;
}

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;
unsigned char addr[110] = {};
okl_long alen = 0;
if (const int rc = local_address(base, name, len, addr, alen); rc != kal_ok) return rc;
const okl_long fd = okl::sys(okl::nr_socket, 1 /* AF_UNIX */, okl::sock_stream | okl::sock_cloexec, 0);
if (okl::failed(fd)) return okl::translate(fd);
if (const okl_long r = okl::sys(okl::nr_bind, fd, reinterpret_cast<okl_long>(addr), alen); okl::failed(r)) {
okl::sys(okl::nr_close, fd);
return r == -98 /* EADDRINUSE */ ? kal_err_exists : okl::translate(r);
}
if (const okl_long r = okl::sys(okl::nr_listen, fd, 128); okl::failed(r)) {
okl::sys(okl::nr_close, fd);
return okl::translate(r);
}
out->h = okl::pack(static_cast<int>(fd));
if (out->h == 0) { okl::sys(okl::nr_close, fd); return kal_err_no_memory; }
return kal_ok;
}

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;
unsigned char addr[110] = {};
okl_long alen = 0;
if (const int rc = local_address(base, name, len, addr, alen); rc != kal_ok) return rc;
const okl_long fd = okl::sys(okl::nr_socket, 1 /* AF_UNIX */, okl::sock_stream | okl::sock_cloexec, 0);
if (okl::failed(fd)) return okl::translate(fd);
for (;;) {
const okl_long r = okl::sys(okl::nr_connect, fd, reinterpret_cast<okl_long>(addr), alen);
if (okl::interrupted(r)) continue;
if (okl::failed(r)) {
okl::sys(okl::nr_close, fd);
// Nothing listening: no name, or a name a listener left behind.
return (r == -okl::e_noent || r == -111 /* ECONNREFUSED */) ? kal_err_not_found
: okl::translate(r);
}
break;
}
out->h = okl::pack(static_cast<int>(fd));
if (out->h == 0) { okl::sys(okl::nr_close, fd); return kal_err_no_memory; }
return kal_ok;
}

kal_stream kal_net_stream(kal_net_conn c) {
// The bare descriptor, for the reason kal_fs_stream gives: openkal.stream's
// operations take whatever the environment's transfer calls take, and a
Expand Down
Loading
Loading