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
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,16 @@ jobs:
rm -f "$RUNNER_TEMP/android.bad"
echo "both Android ABIs build from this implementation unchanged, and name no C library symbol"

# A THIRTY-TWO BIT BUILD RUNS (openkal 0.25.1, clause 5.5). The same
# sources, standalone, for i386: built with the toolchain's clang and lld
# and RUN here, since an x86_64 kernel executes a static i386 program. A
# file past four gibibytes, a task with its own thread-local storage and a
# sleep through the sixty-four bit clock; the conformance suite runs for
# i686 where the family's C library and C++ runtime are built for it.
- name: An i386 build of this implementation runs
if: matrix.toolchain == 'llvm@22.1.8'
run: OPENKAL_INCLUDE="$PWD/.spec/include" bash tools/word32-check.sh

# A checker is only useful if it fails when it should.
- name: The independence check detects a dependence
run: |
Expand Down
37 changes: 33 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,37 @@ for Linux, written on the kernel's own system-call interface.
openkal = "0.25.0"

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

## What 0.26.2 adds

**i386** (openkal 0.25.1, clause 5.5). The kernel's i386 gate (`int $0x80`, the sixth
argument in `ebp`) and its own table of numbers; where a call has a sixty-four bit
time form that form is used (`clock_gettime64`, `ppoll_time64`, `futex_time64`,
`utimensat_time64`, `clock_nanosleep_time64`), and the status of a node comes from
`statx`, so no answer ends in 2038. The calls whose arguments differ between
architectures --- a position in two words (`_llseek`, `mmap2`, `pread64`,
`ftruncate64`), `statfs64`, `O_LARGEFILE` --- are functions of `src/sys.h` that every
caller uses on every architecture. The thread pointer is a segment set by
`set_thread_area` and given to a new context by `clone`. A handle is a thirty-two
bit word there: seventeen bits of descriptor and fifteen of generation; a program's
handle carries its number and its kind, and an opened program's descriptor.
The kernel's records whose layout follows the word are read at the word's offsets:
`waitid`'s `siginfo_t` (its union at twelve, not sixteen), the signal set
(sixty-four bits, `rt_sigprocmask` refused four bytes), and a handler is installed
with `SA_SIGINFO` so the kernel builds the frame `rt_sigreturn` leaves.
`tools/word32-check.sh` builds the implementation standalone for i386 and runs
`tests/word32/runs.cpp`: a file past four gibibytes, a task with its own
thread-local storage, a sleep through the sixty-four bit clock. Measured locally
with the family built for `i686-linux-musl` (openkal-musl and openkal-llvm-runtime
for i386): the specification's conformance suite, `full,optional`, 408 held and
none did not, the five not observed being x86_64's five.

Also: one record of `rt_sigaction` (the mask is the kernel's sixty-four bit set; it
was declared three times with an `unsigned long` mask) and one return from a
handler (it was written twice).

## What 0.26.1 corrects

**A handle is retired before its descriptor is closed.** A connection, a listener
Expand Down Expand Up @@ -398,9 +426,10 @@ rather than call.

## Architectures

`x86_64` and `aarch64`. Adding one is `src/sys.h`: a calling convention, a table
of system-call numbers, the kernel's `struct stat`, and the two thread-pointer
conventions in `src/tls.h`.
`x86_64`, `aarch64` and `i386`. Adding one is `src/sys.h`: a calling convention, a
table of system-call numbers, the kernel's `struct stat` (or `statx`), the
functions for the calls whose arguments the architecture lays out otherwise, and
the thread-pointer convention in `src/tls.h`.

## Verification

Expand Down
7 changes: 6 additions & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-linux"
version = "0.26.1"
version = "0.26.2"
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 @@ -94,6 +94,11 @@ flags = [
"-fno-asynchronous-unwind-tables"] },
]

# tests/word32/ is built standalone for i386 by tools/word32-check.sh, with the C library facilities
# it supplies itself; the host's test programs are the others.
[test]
discover = ["tests/**/*.cpp", "!tests/word32/**"]

[features]
default = []

Expand Down
3 changes: 1 addition & 2 deletions src/env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ bool recover(char*** argv_out, int* argc_out, char*** envp_out) {
// 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 k_sigaction { void* handler; unsigned long flags; void* restorer; unsigned long mask; };
using okl::k_sigaction;
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 */,
Expand Down
10 changes: 5 additions & 5 deletions src/exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,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), page());
const okl_long r = okl::sys(okl::nr_mmap, 0, static_cast<okl_long>(bytes),
const okl_long r = okl::mmap( 0, static_cast<okl_long>(bytes),
okl::prot_read | okl::prot_write,
okl::map_private | okl::map_anonymous, -1, 0);
if (okl::failed(r)) return nullptr;
Expand All @@ -78,7 +78,7 @@ void* kal_exec_alloc_at(void* at, kal_uintptr size) {
const okl_uptr where = reinterpret_cast<okl_uptr>(at);
if (size == 0 || where == 0 || (where & (page() - 1)) != 0) return nullptr;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
const okl_long r = okl::sys(okl::nr_mmap, static_cast<okl_long>(where), static_cast<okl_long>(bytes),
const okl_long r = okl::mmap( static_cast<okl_long>(where), static_cast<okl_long>(bytes),
okl::prot_read | okl::prot_write,
okl::map_private | okl::map_anonymous | 0x100000 /* MAP_FIXED_NOREPLACE */,
-1, 0);
Expand Down Expand Up @@ -189,7 +189,7 @@ int kal_exec_map_part(void* region, kal_uintptr offset, kal_uintptr size, kal_fi
|| ((how & KAL_EXEC_MAP_WRITE) && (how & KAL_EXEC_MAP_EXECUTE)))
return kal_err_invalid;
okl::kstat st{};
const okl_long s = okl::sys(okl::nr_fstat, fd, reinterpret_cast<okl_long>(&st));
const okl_long s = okl::fstat(fd, &st);
if (okl::failed(s)) return okl::translate(s);
if ((st.mode & okl::s_ifmt) != okl::s_ifreg) return kal_err_not_supported;
const kal_u64 length = st.size > 0 ? static_cast<kal_u64>(st.size) : 0;
Expand All @@ -205,10 +205,10 @@ int kal_exec_map_part(void* region, kal_uintptr offset, kal_uintptr size, kal_fi
| ((how & KAL_EXEC_MAP_WRITE) ? okl::prot_write : 0)
| (bti ? 0x10 /* PROT_BTI */ : 0);
const okl_long populate = (how & KAL_EXEC_MAP_PREFETCH) ? 0x8000 /* MAP_POPULATE */ : 0;
const okl_long r = okl::sys(okl::nr_mmap,
const okl_long r = okl::mmap(
reinterpret_cast<okl_long>(static_cast<char*>(region) + offset),
static_cast<okl_long>(size), prot, okl::map_private | okl::map_fixed | populate, fd,
static_cast<okl_long>(at));
static_cast<okl_i64>(at));
if (okl::failed(r)) {
// ENODEV: the file system does not map. EPERM: its files are not
// executed (a volume mounted noexec). The resource refuses, not the
Expand Down
50 changes: 21 additions & 29 deletions src/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,7 @@ bool granted(preopen* t, kal_uintptr* n) {
for (okl_uptr i = 0; i < len; ++i) if (v[i] == '\0') return false;

okl::kstat st{};
const bool dir = !okl::failed(okl::sys(okl::nr_fstat, static_cast<okl_long>(fd),
reinterpret_cast<okl_long>(&st)))
const bool dir = !okl::failed(okl::fstat(static_cast<okl_long>(fd), &st))
&& (st.mode & okl::s_ifmt) == okl::s_ifdir;
if (dir) okl::sys(okl::nr_fcntl, static_cast<okl_long>(fd), okl::f_setfd, okl::fd_cloexec);
t[k++] = { v, len, dir ? okl::pack(static_cast<int>(fd)) : 0u };
Expand Down Expand Up @@ -180,9 +179,9 @@ preopen* table(kal_uintptr* count) {
if (!okl::failed(n) && n > 1) cwd_len = static_cast<okl_uptr>(n) - 1;
else { g_cwd[0] = '.'; g_cwd[1] = '\0'; cwd_len = 1; }

const okl_long fd0 = okl::sys(okl::nr_openat, okl::at_fdcwd,
const okl_long fd0 = okl::openat( okl::at_fdcwd,
reinterpret_cast<okl_long>("."), dir_handle_flags, 0);
const okl_long fd1 = okl::sys(okl::nr_openat, okl::at_fdcwd,
const okl_long fd1 = okl::openat( okl::at_fdcwd,
reinterpret_cast<okl_long>("/"), dir_handle_flags, 0);
t[0] = { g_cwd, cwd_len, okl::failed(fd0) ? 0u : okl::pack(static_cast<int>(fd0)) };
t[1] = { "/", 1, okl::failed(fd1) ? 0u : okl::pack(static_cast<int>(fd1)) };
Expand Down Expand Up @@ -222,10 +221,10 @@ kal_uintptr volume_props(int fd) {
if (fd < 0) return 0;

okl::kstatfs sf{};
const okl_long r = okl::sys(okl::nr_fstatfs, fd, reinterpret_cast<okl_long>(&sf));
const okl_long r = okl::fstatfs(fd, &sf);
if (okl::failed(r)) return conservative;

switch (sf.f_type) {
switch (static_cast<okl_ulong>(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
Expand Down Expand Up @@ -293,7 +292,7 @@ kal_uintptr volume_props_of(int fd) { return volume_props(fd); }
// asked about, so a reader takes the sequence before and after reading them and
// reads again if a writer was between; writers take the lock. The lock taken on
// every enquiry was a fifth of what an enquiry cost beyond the kernel's own.
struct volume_answer { okl_u64 dev; kal_uintptr props; bool known; };
struct volume_answer { alignas(8) okl_u64 dev; kal_uintptr props; bool known; }; // aligned: an atomic word on i386 too
volume_answer g_volumes[8];
unsigned g_volume_next = 0;
volatile int g_volume_lock = 0;
Expand Down Expand Up @@ -534,7 +533,7 @@ int kal_fs_open_dir(kal_dir base, const char* name, kal_uintptr len, kal_dir* ou
asked(b, false, name, len);
return kal_err_not_found;
}
const okl_long fd = okl::sys(okl::nr_openat, b, reinterpret_cast<okl_long>(t.buf),
const okl_long fd = okl::openat( b, reinterpret_cast<okl_long>(t.buf),
dir_handle_flags, 0);
if (okl::failed(fd)) {
if (rights && (fd == -okl::e_noent || fd == -okl::e_notdir)) asked(b, false, name, len);
Expand Down Expand Up @@ -574,7 +573,7 @@ int kal_fs_open(kal_dir base, const char* name, kal_uintptr len,
asked(b, false, name, len);
return kal_err_not_found;
}
const okl_long fd = okl::sys(okl::nr_openat, b, reinterpret_cast<okl_long>(t.buf), f, 0666);
const okl_long fd = okl::openat( b, reinterpret_cast<okl_long>(t.buf), f, 0666);
if (okl::failed(fd)) {
if (rights && (fd == -okl::e_noent || fd == -okl::e_notdir)) asked(b, false, name, len);
return okl::translate(fd);
Expand Down Expand Up @@ -616,16 +615,16 @@ int kal_fs_seek(kal_file f, kal_i64 offset, int whence, kal_u64* result) {
int w = 0;
if (whence == KAL_SEEK_CURRENT) w = 1;
else if (whence == KAL_SEEK_END) w = 2;
const okl_long r = okl::sys(okl::nr_lseek, fd, static_cast<okl_long>(offset), w);
if (okl::failed(r)) return okl::translate(r);
const okl_i64 r = okl::lseek(fd, static_cast<okl_i64>(offset), w);
if (r < 0) return okl::translate(static_cast<okl_long>(r));
if (result) *result = static_cast<kal_u64>(r);
return kal_ok;
}

int kal_fs_truncate(kal_file f, kal_u64 size) {
const int fd = okl::unpack(f.h);
if (fd < 0) return kal_err_invalid;
const okl_long r = okl::sys(okl::nr_ftruncate, fd, static_cast<okl_long>(size));
const okl_long r = okl::ftruncate(fd, static_cast<okl_i64>(size));
return okl::failed(r) ? okl::translate(r) : kal_ok;
}

Expand All @@ -638,8 +637,7 @@ kal_intptr kal_fs_read_at(kal_file f, void* buf, kal_uintptr len, kal_u64 offset
const int fd = okl::unpack(f.h);
if (fd < 0) return -kal_err_invalid;
for (;;) {
const okl_long r = okl::sys(okl::nr_pread64, fd, reinterpret_cast<okl_long>(buf),
static_cast<okl_long>(len), static_cast<okl_long>(offset));
const okl_long r = okl::pread(fd, buf, static_cast<okl_uptr>(len), static_cast<okl_i64>(offset));
if (okl::interrupted(r)) continue;
if (okl::failed(r)) return -okl::translate(r);
return static_cast<kal_intptr>(r);
Expand All @@ -652,9 +650,7 @@ kal_intptr kal_fs_write_at(kal_file f, const void* buf, kal_uintptr len, kal_u64
const auto* p = static_cast<const unsigned char*>(buf);
kal_uintptr done = 0;
while (done < len) {
const okl_long r = okl::sys(okl::nr_pwrite64, fd, reinterpret_cast<okl_long>(p + done),
static_cast<okl_long>(len - done),
static_cast<okl_long>(offset + done));
const okl_long r = okl::pwrite(fd, p + done, static_cast<okl_uptr>(len - done), static_cast<okl_i64>(offset + done));
if (okl::interrupted(r)) continue;
if (okl::failed(r)) {
if (done != 0) return static_cast<kal_intptr>(done);
Expand Down Expand Up @@ -686,8 +682,7 @@ int kal_fs_info(kal_dir base, const char* name, kal_uintptr len,
// A C library above reported a link where the host reports a regular file,
// and one symbolic link made a whole tree uncopyable.
const okl_long at = (flags & KAL_FS_NO_RESOLVE) ? okl::at_symlink_nofollow : 0;
const okl_long r = okl::sys(okl::nr_newfstatat, b, reinterpret_cast<okl_long>(t.buf),
reinterpret_cast<okl_long>(&st), at);
const okl_long r = okl::fstatat(b, t.buf, &st, at);
if (okl::failed(r)) {
// Clause 7.7: enquiry about a name that does not exist is answered,
// not refused. A component of the name that is not a directory is the
Expand All @@ -709,7 +704,7 @@ int kal_fs_file_info(kal_file f, kal_u32 wanted, kal_node_info* out) {
const int fd = okl::unpack(f.h);
if (fd < 0 || !info_ok(out)) return kal_err_invalid;
okl::kstat st{};
const okl_long r = okl::sys(okl::nr_fstat, fd, reinterpret_cast<okl_long>(&st));
const okl_long r = okl::fstat(fd, &st);
if (okl::failed(r)) return okl::translate(r);
fill_info(st, wanted, out, fd);
return kal_ok;
Expand Down Expand Up @@ -777,8 +772,7 @@ int kal_fs_set_executable_at(kal_dir base, const char* name, kal_uintptr len,
if (refused_write(b)) return kal_err_permission;

okl::kstat st{};
okl_long r = okl::sys(okl::nr_newfstatat, b, reinterpret_cast<okl_long>(t.buf),
reinterpret_cast<okl_long>(&st), 0);
okl_long r = okl::fstatat(b, t.buf, &st, 0);
if (okl::failed(r)) return okl::translate(r);
const int k = kind_of(st.mode);
if (k == kal_node_directory) return kal_err_is_directory;
Expand Down Expand Up @@ -872,7 +866,7 @@ int kal_fs_rename_flags(kal_dir from, const char* a, kal_uintptr alen,
int kal_fs_list_begin(kal_dir d, kal_uintptr* iter) {
const int fd = okl::unpack(d.h);
if (fd < 0 || iter == nullptr) return kal_err_invalid;
const okl_long own = okl::sys(okl::nr_openat, fd, reinterpret_cast<okl_long>("."),
const okl_long own = okl::openat( fd, reinterpret_cast<okl_long>("."),
okl::o_rdonly | okl::o_directory | okl::o_cloexec, 0);
if (okl::failed(own)) return okl::translate(own);
auto* s = static_cast<listing*>(kal_alloc(sizeof(listing), alignof(listing)));
Expand Down Expand Up @@ -1040,7 +1034,7 @@ int kal_fs_capacity(kal_dir d, kal_u64* total, kal_u64* available) {
if (fd < 0) return kal_err_invalid;

okl::kstatfs sf{};
const okl_long r = okl::sys(okl::nr_fstatfs, fd, reinterpret_cast<okl_long>(&sf));
const okl_long r = okl::fstatfs(fd, &sf);
if (okl::failed(r)) return okl::translate(r);

// In bytes, because that is what the interface says and what a caller of it
Expand Down Expand Up @@ -1090,8 +1084,7 @@ int kal_fs_hard_link(kal_dir from, const char* a, kal_uintptr alen,
if (!ta.ok || !tb.ok) return kal_err_invalid;
if (refused_write(t2)) return kal_err_permission;
okl::kstat st{};
const okl_long s = okl::sys(okl::nr_newfstatat, f, reinterpret_cast<okl_long>(ta.buf),
reinterpret_cast<okl_long>(&st), okl::at_symlink_nofollow);
const okl_long s = okl::fstatat(f, ta.buf, &st, okl::at_symlink_nofollow);
if (okl::failed(s)) return okl::translate(s);
if ((st.mode & okl::s_ifmt) == okl::s_ifdir) return kal_err_is_directory;
const okl_long r = okl::sys(okl::nr_linkat, f, reinterpret_cast<okl_long>(ta.buf),
Expand All @@ -1118,7 +1111,7 @@ kal_intptr kal_fs_resolved_name(kal_dir base, const char* name, kal_uintptr len,
asked(b, false, name, len);
return -kal_err_not_found;
}
const okl_long fd = okl::sys(okl::nr_openat, b, reinterpret_cast<okl_long>(t.buf),
const okl_long fd = okl::openat( b, reinterpret_cast<okl_long>(t.buf),
okl::o_path | okl::o_cloexec, 0);
if (okl::failed(fd)) {
if (rights && (fd == -okl::e_noent || fd == -okl::e_notdir)) asked(b, false, name, len);
Expand Down Expand Up @@ -1198,8 +1191,7 @@ int kal_fs_list_next_info(kal_dir d, kal_uintptr* iter, char* name_out, kal_uint
if ((wanted & ~KAL_INFO_KIND) != 0) {
okl::kstat st{};
const int fd = okl::unpack(d.h);
if (!okl::failed(okl::sys(okl::nr_newfstatat, fd, reinterpret_cast<okl_long>(own),
reinterpret_cast<okl_long>(&st), okl::at_symlink_nofollow))) {
if (!okl::failed(okl::fstatat(fd, own, &st, okl::at_symlink_nofollow))) {
v.self_size = self;
fill_info(st, wanted, &v, fd);
v.kind = kind;
Expand Down
Loading
Loading