SchOS/ldls/kpi/string.c
Nexoniarz 9c729edac1 Cheaper cursor, a fortify panic that names its function; Finch's gap
The cursor now costs nothing for a byte inside an escape sequence and
one cell fewer for a plain glyph: the grid is the truth, so the old
cursor cell is repainted after the byte, and not at all when a glyph
has just been drawn over it.

__fortify_panic says "r8169: rtl_open.cold+0x..." through ldlsd's module
list, so a photograph of the screen is enough, and exits instead of
spinning on a core and holding /dev/netdev.

docs/plan/finch.md: only integrating Finch is later; finishing it is
not. The self-hosting gap measured against Finch's own bootstrap test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015f73FesGZGYPkmDvUcj6QX
2026-10-25 18:31:40 +01:00

166 lines
5 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// The string and memory primitives. See ldls/kpi/kpi.h for the provenance rule.
//
// These are the least interesting functions in the layer and the most
// dangerous to leave out, because a stub for one of them does not crash --
// it silently does nothing, and the caller carries on with a buffer full of
// whatever was there before.
//
// That is not hypothetical. `memcpy` was a logging stub while the receive path
// was being brought up: the driver copied each frame into its skb, the copy
// did not happen, and the stack was handed the right *length* of zeroes. Every
// counter said packets were arriving and DHCP never completed. A crash would
// have been easier to find.
#include "core/types.h"
#include "uapi/user.h"
#include "kpi.h"
void *
kpi_memcpy(void *d, const void *s, unsigned long n)
{
unsigned char *a = (unsigned char *)d;
const unsigned char *b = (const unsigned char *)s;
while (n--) *a++ = *b++;
return d;
}
void *
kpi_memmove(void *d, const void *s, unsigned long n)
{
unsigned char *a = (unsigned char *)d;
const unsigned char *b = (const unsigned char *)s;
if (a == b || n == 0)
return d;
if (a < b) {
while (n--) *a++ = *b++;
} else {
a += n; b += n;
while (n--) *--a = *--b;
}
return d;
}
void *
kpi_memset(void *d, int c, unsigned long n)
{
unsigned char *a = (unsigned char *)d;
while (n--) *a++ = (unsigned char)c;
return d;
}
int
kpi_memcmp(const void *x, const void *y, unsigned long n)
{
const unsigned char *a = x, *b = y;
while (n--) { if (*a != *b) return (int)*a - (int)*b; a++; b++; }
return 0;
}
// A null string is survivable, and reported.
//
// A hosted driver reads names out of structures this host only partly fills
// in, and the first one it found was `pci_name(pdev)` -- a pointer into a
// `struct device` that had never been given one. Faulting inside strnlen tells
// you nothing about which field was missing; answering zero and saying so
// leaves the driver running and names the problem.
static long kpi_nullstr;
static int
str_ok(const char *s, const char *who)
{
if (s)
return 1;
if (++kpi_nullstr <= 4) {
kpi_log(" kpi: "); kpi_log(who);
kpi_log(" on a null string -- some field of a struct this host fills in "
"only partly\n");
}
return 0;
}
unsigned long
kpi_strlen(const char *s)
{
unsigned long n = 0;
if (!str_ok(s, "strlen")) return 0;
while (s[n]) n++;
return n;
}
unsigned long
kpi_strnlen(const char *s, unsigned long max)
{
unsigned long n = 0;
if (!str_ok(s, "strnlen")) return 0;
while (n < max && s[n]) n++;
return n;
}
// sized_strscpy(dest, src, count) -- a bounded copy that always terminates and
// reports the length copied, or -E2BIG when it did not fit.
long
kpi_sized_strscpy(char *d, const char *s, unsigned long n)
{
unsigned long i = 0;
if (n == 0)
return -7; /* -E2BIG */
if (!str_ok(s, "strscpy")) { d[0] = 0; return 0; }
while (i + 1 < n && s[i]) { d[i] = s[i]; i++; }
d[i] = 0;
return s[i] ? -7 : (long)i;
}
// The io variants. On this platform a BAR is ordinary memory once mapped, so
// these differ from their plain counterparts only in that the compiler must
// not reorder or elide them -- which is what the volatile is for.
void
kpi_memcpy_fromio(void *d, const void *src, unsigned long n)
{
unsigned char *a = (unsigned char *)d;
const volatile unsigned char *b = (const volatile unsigned char *)src;
while (n--) *a++ = *b++;
}
void
kpi_memcpy_toio(void *dst, const void *s, unsigned long n)
{
volatile unsigned char *a = (volatile unsigned char *)dst;
const unsigned char *b = (const unsigned char *)s;
while (n--) *a++ = *b++;
}
// __fortify_panic is what a driver's bounds-checked string helpers call when
// they detect an overflow. Reaching it means the driver found a bug in itself,
// and the honest thing is to say so rather than return.
void (*kpi_where)(unsigned long addr);
void
kpi_fortify_panic(unsigned int reason, unsigned long avail, unsigned long size)
{
kpi_log("kpi: the driver called __fortify_panic -- it detected an "
"overflow in itself\n");
// Which check, how much it thought it had, how much it wanted, and where
// from -- the address is in the driver's own text, so subtract the load
// address logged for it and look the offset up with nm. This took a
// hardware round trip to find the last time it said nothing.
kpi_log(" reason "); kpi_lognum(reason);
kpi_log(", available "); kpi_lognum((long long)avail);
kpi_log(", wanted "); kpi_lognum((long long)size);
kpi_log(", from ");
if (kpi_where)
kpi_where((unsigned long)__builtin_return_address(0));
else {
kpi_log("0x"); kpi_loghex((unsigned long)__builtin_return_address(0), 16);
}
kpi_log("\n");
// Not spin: a driver that has found a bug in itself holds /dev/netdev and
// one core at 100% until reboot. The job is KeepAlive = no, so leaving is
// free, and the kernel takes the interface back.
exit_group(1);
for (;;)
;
}