Skip to content

Commit 88b82e0

Browse files
authored
0.17.0 --- an enumeration ends with its directory, a context has the stack it asks for, and input can arrive unaltered (#37)
Fixes #34. Supersedes #35, whose commit is carried with its author. - **An enumeration ends when its directory is closed** (#35), and `kal_fs_list_next` refuses a closed directory or an iterator that is not an open listing of the directory given, without reading through it (clause 6.7). A close that races a read of the same enumeration marks the listing and the reader releases it (clause 6.6). - **A started context's stack** is 8 MiB unless more is asked for, with a 256 KiB inaccessible guard beneath it; the guard exceeds the 69,008-byte frame measured in the C library above at `-O0`. `kal_task_start_sized` (openkal 0.16) gives at least the size asked for and never less than the default. - **`KAL_TERM_PASS_INPUT`** is `ISTRIP | INLCR | IGNCR | ICRNL`: read as set only where all four are clear; set, all four are cleared; clear, `ICRNL` is restored. Observed in this package's tests: the leak through `/proc/self/fd`, the refusals, two hundred close/read races; a pseudo terminal delivering the return key and Ctrl+J as 0x0d and 0x0a with the position set; a least stack size as room for 1, 16 and 64 MiB. The specification's suite at `openkal-0.16.0`: 201 held, 0 did not hold.
1 parent e554292 commit 88b82e0

9 files changed

Lines changed: 518 additions & 26 deletions

File tree

‎README.md‎

Lines changed: 18 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.15.0"
8+
openkal = "0.16.0"
99

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

1414
## Why it does not use a C library
@@ -45,6 +45,22 @@ second and third.
4545
All eight. `tools/check-surface.sh --complete` in the specification package
4646
compares the exported names against `SURFACE.txt`.
4747

48+
## The stack a started context is given
49+
50+
Where this implementation creates a context (the `standalone` arrangement) it
51+
maps the stack itself: 8 MiB unless more is asked for, which is the size the
52+
host's own C library gives a thread, and a 256 KiB inaccessible guard beneath
53+
it. The mapping is committed as it is touched, so the size costs address space
54+
rather than memory. The guard is larger than one page because a frame of 69,008
55+
bytes was measured in the C library above at `-O0`, and a frame larger than the
56+
guard steps over it.
57+
58+
`kal_task_start_sized` (openkal 0.16) gives at least the size asked for and
59+
never less than the default, so a C library that asks for its own smaller
60+
default on every thread does not shrink what each one had. A request larger than
61+
the address space is `kal_err_no_memory`. Where the program carries a runtime of
62+
its own, the request is passed to that runtime.
63+
4864
## The region a context stands on
4965

5066
`kal_task_stack` reports the stack of the calling context, and this

‎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.16.1"
4+
version = "0.17.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

@@ -63,7 +63,7 @@ provides-interfaces = [
6363
]
6464

6565
[dependencies]
66-
openkal = "0.15.0"
66+
openkal = "0.16.0"
6767

6868
# The package contributes definitions and no modules. The interface it
6969
# implements is declared by the specification package, which this package

‎src/fs.cpp‎

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,98 @@ kal_uintptr put_name(const char* src, kal_uintptr n,
254254
// calling.
255255
struct listing {
256256
int fd;
257+
int dir; // the descriptor of the directory it was begun upon
258+
listing* next;
259+
int busy; // a context is inside kal_fs_list_next with it
260+
int doomed; // its directory was closed while it was busy
257261
okl_uptr used;
258262
okl_uptr pos;
259263
char buf[8192];
260264
};
261265

266+
// THE ENUMERATIONS NOT YET READ TO THE END, BECAUSE CLOSING THE DIRECTORY MUST
267+
// END THEM. fs.h releases an iterator "by reading past the end or by closing
268+
// the directory that produced it", and the descriptor a listing holds is its
269+
// own, so closing the directory's descriptor does not reach it. Measured before
270+
// this list existed: a C library above that opened a directory larger than its
271+
// buffer, read one entry and closed it, 64 times, was left holding 64 more
272+
// descriptors.
273+
//
274+
// The lock spins rather than suspends, because suspending would make openkal.fs
275+
// depend upon openkal.task, as src/memory.cpp says of its own.
276+
listing* g_listings = nullptr;
277+
volatile int g_listings_held = 0;
278+
279+
void listings_lock() {
280+
while (__atomic_exchange_n(&g_listings_held, 1, __ATOMIC_ACQUIRE)) okl::sys(okl::nr_sched_yield);
281+
}
282+
void listings_unlock() { __atomic_store_n(&g_listings_held, 0, __ATOMIC_RELEASE); }
283+
284+
void listing_release(listing* s) {
285+
okl::sys(okl::nr_close, s->fd);
286+
kal_free(s, sizeof(listing), alignof(listing));
287+
}
288+
289+
// Ends every listing begun upon `dir'. What is ended is unlinked under the lock
290+
// and released after it.
291+
//
292+
// A LISTING ANOTHER CONTEXT IS READING IS NOT RELEASED HERE. Clause 6.6 requires
293+
// that concurrent operations upon one handle not damage this implementation's
294+
// state, and releasing the buffer a getdents64 in another context is writing
295+
// into would do exactly that. Such a listing is marked, and the context reading
296+
// it releases it on the way out; it can no longer be found, so nothing reaches
297+
// it after that.
298+
void listings_end(int dir) {
299+
listing* done = nullptr;
300+
listings_lock();
301+
for (listing** p = &g_listings; *p != nullptr;) {
302+
listing* s = *p;
303+
if (s->dir != dir || s->doomed) { p = &s->next; continue; }
304+
if (s->busy) { s->doomed = 1; p = &s->next; continue; }
305+
*p = s->next; s->next = done; done = s;
306+
}
307+
listings_unlock();
308+
while (done != nullptr) { listing* s = done; done = s->next; listing_release(s); }
309+
}
310+
311+
// Takes `s' for the calling context, or reports that it is not a listing of
312+
// `dir' that is still open.
313+
//
314+
// THE WORD IS FOUND RATHER THAN BELIEVED. An iterator is the caller's copy of an
315+
// address this implementation allocated, and closing its directory frees what
316+
// it named --- so a caller that kept it, or that raced the close, holds an
317+
// address that may by now be anything. Searching the open listings for it is
318+
// what lets kal_fs_list_next refuse such a word without reading through it. The
319+
// search is over enumerations not yet read to their end, which a program holds
320+
// a handful of.
321+
bool listing_take(listing* s, int dir) {
322+
for (;;) {
323+
listings_lock();
324+
listing* p = g_listings;
325+
while (p != nullptr && p != s) p = p->next;
326+
if (p == nullptr || p->dir != dir || p->doomed) { listings_unlock(); return false; }
327+
if (!p->busy) { p->busy = 1; listings_unlock(); return true; }
328+
// Two contexts reading one enumeration take turns; the order in which
329+
// they receive its entries is the unspecified order clause 6.6 permits.
330+
listings_unlock();
331+
okl::sys(okl::nr_sched_yield);
332+
}
333+
}
334+
335+
// Gives `s' back, and releases it if it reached its end or its directory was
336+
// closed while it was being read.
337+
void listing_give(listing* s, bool ended) {
338+
listings_lock();
339+
s->busy = 0;
340+
const bool release = ended || s->doomed;
341+
if (release) {
342+
for (listing** p = &g_listings; *p != nullptr; p = &(*p)->next)
343+
if (*p == s) { *p = s->next; break; }
344+
}
345+
listings_unlock();
346+
if (release) listing_release(s);
347+
}
348+
262349
} // namespace
263350

264351
extern "C" {
@@ -312,7 +399,11 @@ int kal_fs_open(kal_dir base, const char* name, kal_uintptr len,
312399

313400
void kal_fs_close_dir(kal_dir d) {
314401
const int fd = okl::unpack(d.h);
315-
if (fd >= 0) { okl::retire(d.h); okl::sys(okl::nr_close, fd); }
402+
if (fd < 0) return;
403+
// Before the close, because after it the number may already name another
404+
// directory whose enumerations are not this one's.
405+
listings_end(fd);
406+
okl::retire(d.h); okl::sys(okl::nr_close, fd);
316407
}
317408

318409
void kal_fs_close_file(kal_file f) {
@@ -515,25 +606,31 @@ int kal_fs_list_begin(kal_dir d, kal_uintptr* iter) {
515606
if (okl::failed(own)) return okl::translate(own);
516607
auto* s = static_cast<listing*>(kal_alloc(sizeof(listing), alignof(listing)));
517608
if (s == nullptr) { okl::sys(okl::nr_close, own); return kal_err_no_memory; }
518-
s->fd = static_cast<int>(own); s->used = 0; s->pos = 0;
609+
s->fd = static_cast<int>(own); s->dir = fd; s->busy = 0; s->doomed = 0;
610+
s->used = 0; s->pos = 0;
611+
listings_lock(); s->next = g_listings; g_listings = s; listings_unlock();
519612
*iter = reinterpret_cast<kal_uintptr>(s);
520613
return kal_ok;
521614
}
522615

523-
int kal_fs_list_next(kal_dir, kal_uintptr* iter,
616+
int kal_fs_list_next(kal_dir d, kal_uintptr* iter,
524617
char* name_out, kal_uintptr name_cap,
525618
kal_uintptr* name_len, int* kind) {
526-
if (iter == nullptr || *iter == 0) return kal_err_invalid;
619+
// A CLOSED DIRECTORY IS A RELEASED HANDLE (clause 6.7), and closing it
620+
// released the iterator too, which fs.h states. Both are refused here
621+
// without the iterator being read through.
622+
const int fd = okl::unpack(d.h);
623+
if (fd < 0 || iter == nullptr || *iter == 0) return kal_err_invalid;
527624
auto* s = reinterpret_cast<listing*>(*iter);
625+
if (!listing_take(s, fd)) return kal_err_invalid;
528626
for (;;) {
529627
if (s->pos >= s->used) {
530628
const okl_long r = okl::sys(okl::nr_getdents64, s->fd,
531629
reinterpret_cast<okl_long>(s->buf),
532630
static_cast<okl_long>(sizeof s->buf));
533631
if (okl::interrupted(r)) continue;
534632
if (okl::failed(r) || r == 0) {
535-
okl::sys(okl::nr_close, s->fd);
536-
kal_free(s, sizeof(listing), alignof(listing));
633+
listing_give(s, true);
537634
*iter = 0;
538635
if (name_len) *name_len = 0;
539636
return okl::failed(r) ? okl::translate(r) : kal_ok;
@@ -551,6 +648,7 @@ int kal_fs_list_next(kal_dir, kal_uintptr* iter,
551648
if (kind) *kind = e->type == okl::dt_dir ? kal_node_directory
552649
: e->type == okl::dt_reg ? kal_node_file
553650
: e->type == okl::dt_lnk ? kal_node_link : kal_node_other;
651+
listing_give(s, false);
554652
return kal_ok;
555653
}
556654
}

‎src/sys.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,14 @@ enum : okl_u32 {
534534
t_isig = 0000001u, t_icanon = 0000002u, t_echo = 0000010u, t_iexten = 0100000u,
535535
};
536536
enum : okl_u32 { t_ixon = 0002000u };
537+
// `t_istrip', `t_inlcr', `t_igncr' and `t_icrnl' are the mechanisms by which
538+
// this kernel alters input on its way: clearing the high bit, rewriting a line
539+
// feed into a carriage return, discarding a carriage return, and rewriting a
540+
// carriage return into a line feed. KAL_TERM_PASS_INPUT is the whole of them,
541+
// for the reason given above for the three that keep a keystroke. The last is
542+
// the one this kernel applies ordinarily, and the one that made the return key
543+
// and Ctrl+J the same byte (mcpplibs/openkal#48).
544+
enum : okl_u32 { t_istrip = 0000040u, t_inlcr = 0000100u, t_igncr = 0000200u, t_icrnl = 0000400u };
537545
enum { v_time = 5, v_min = 6 };
538546

539547
// --- openkal.net and openkal.datagram --------------------------------------

0 commit comments

Comments
 (0)