SchOS/cmd/resolvconf/main.c
Nexoniarz e659c6b187 bug pass: whole console lines, a quiet SCI, an honest reserve warning
- schinit, schinitd and vgpud build every message from pieces, one
  write() each, so a job starting on another CPU landed mid-line:
  "schinitd: starting vgpud: os.schos.resolvconfno virtio-gpu on this
  machine". They now collect a line and write it at the newline;
  resolvconf's verbose line is one write with its prefix.
- The ACPI SCI registers IRQ_QUIET. A desktop's SCI never fires, and the
  poller took two seconds of silence for a misrouted line ("irq 9:
  nothing has arrived on it") and called the handler every tick. A line
  is only exempt while every handler on it is quiet.
- The loader claims each segment from where the last one's claim ended,
  so two segments sharing a page no longer ask for it twice. When a
  claim is still refused, it names the memory-map entries in the way.
- Stale divesymlink() declaration gone; the kernel.md struct table
  re-measured (inode, buf and file pools grow from slabs now).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 16:03:33 +02:00

195 lines
6.5 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// resolvconf -- put the nameserver where the resolver will look.
//
// SchOS has no DNS client, and the interesting part is that it does not need
// one. musl contains a complete resolver: `getaddrinfo` reads
// /etc/resolv.conf, sends a query over an ordinary UDP socket and parses the
// reply, and every one of those pieces already worked here. The single missing
// fact was *which* nameserver -- which arrives in a DHCP reply the kernel
// handled and never told anyone about.
//
// That is what made `wget` look broken. It was not wget, and it was not the
// core utilities: nothing on this system could turn a name into an address, so
// every program that took a hostname failed the same way and each one looked
// like its own separate bug.
//
// resolvconf wait for a nameserver, write it, exit
// resolvconf -n write whatever there is now, do not wait
// resolvconf -p print what the kernel has, change nothing
//
// The kernel publishes /proc/resolv.conf and stops there, deliberately: it
// renders resolv.conf's exact syntax so this is a copy rather than a
// translation, and *whether* /etc/resolv.conf should be overwritten is a
// policy question. A machine with a hand-written one should keep it, which is
// why -f exists and why it is not the default.
#include "core/types.h"
#include "uapi/user.h"
#include "uapi/fcntl.h"
#define SRC "/proc/resolv.conf"
#define DST "/etc/resolv.conf"
// DHCP takes a few seconds on a quiet network and longer on a busy one. Two
// minutes is past any of that and short enough that a machine with no DHCP
// server does not hold a boot job open for ever.
#define WAIT_SECONDS 120
static void
puts_(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void *)s, n);
}
// Whether this boot asked to be told what is happening; see /proc/bootmode and
// kinfo() in kernel/core/console.c for the same distinction one level down.
//
// Success here is progress, not news. Names starting to work is the expected
// outcome of a normal boot, and printing it unconditionally meant a quiet boot
// still opened with a line about the nameserver -- the one line that was
// supposed to be "Welcome to SchOS!". Failures stay on puts_(), because a
// machine where nothing can resolve a hostname should say so at any verbosity.
static int verbose_boot;
static void
read_bootmode(void)
{
char b[16];
int fd = open("/proc/bootmode", O_RDONLY);
int n;
if (fd < 0)
return;
n = read(fd, b, sizeof(b) - 1);
close(fd);
if (n > 0) { b[n] = 0; verbose_boot = (b[0] == 'v'); }
}
static int
same(const char *a, const char *b, int n)
{
for (int i = 0; i < n; i++)
if (a[i] != b[i])
return 0;
return 1;
}
static int
slurp(const char *path, char *buf, int cap)
{
int fd = open((char *)path, O_RDONLY);
int n, len = 0;
if (fd < 0)
return -1;
while (len < cap - 1 && (n = read(fd, buf + len, cap - 1 - len)) > 0)
len += n;
close(fd);
buf[len] = 0;
return len;
}
int
main(int argc, char *argv[])
{
char buf[512];
int i, n, fd, wait = 1, force = 0, print = 0;
read_bootmode();
for (i = 1; i < argc; i++) {
if (argv[i][0] != '-' || argv[i][2] != 0)
goto usage;
switch (argv[i][1]) {
case 'n': wait = 0; break;
case 'f': force = 1; break;
case 'p': print = 1; wait = 0; break;
default:
usage:
puts_("usage: resolvconf [-n] [-f] [-p]\n"
" -n do not wait for DHCP\n"
" -f overwrite an existing " DST "\n"
" -p print what the kernel has and exit\n");
return 1;
}
}
// Wait by polling rather than by blocking on the file: /proc has no
// readiness to report and inventing one for this would be a worse
// interface than a loop that ends.
for (i = 0; ; i++) {
n = slurp(SRC, buf, sizeof(buf));
if (n < 0) {
puts_("resolvconf: no " SRC " -- this kernel has no network\n");
return 1;
}
if (n > 0 || !wait || i >= WAIT_SECONDS)
break;
sleep(100); // one second, at 100 Hz
}
if (print) {
if (n > 0) write(1, buf, n);
else puts_("resolvconf: the kernel has no nameserver yet\n");
return n > 0 ? 0 : 1;
}
if (n == 0) {
puts_("resolvconf: no nameserver after ");
puts_(wait ? "two minutes" : "no wait");
puts_(" -- is there a DHCP server?\n");
return 1;
}
// An existing file is somebody's decision until told otherwise.
//
// Saying so every boot is the wrong volume for it. On a machine that has
// been up before, /etc/resolv.conf always exists and always says the same
// thing, so this printed a line about having done nothing on every single
// start -- which is exactly the kind of message that teaches people to
// stop reading their own boot.
//
// Quiet when the file already says what this would have written. It is
// still worth a word when the two *disagree*, because then the nameserver
// has changed and the machine is using the old one.
if (!force) {
char old[512];
int on = slurp(DST, old, sizeof(old));
if (on > 0) {
if (on == n && same(old, buf, n))
return 0; // already right; nothing to say
puts_("resolvconf: " DST " differs from what DHCP gave;"
" -f to replace it\n");
return 0;
}
}
fd = open(DST, O_WRONLY | O_CREAT | O_TRUNC);
if (fd < 0) {
puts_("resolvconf: cannot write " DST "\n");
return 1;
}
if (write(fd, buf, n) != n) {
puts_("resolvconf: short write to " DST "\n");
close(fd);
return 1;
}
close(fd);
// Worth one line under -v, because it is the moment names start working
// and somebody watching a verbose boot may be waiting to know it.
// One write(), prefix and all: two would let another job's line land
// between them, which a verbose boot on real hardware showed as
// "os.schos.keymapnameserver 192.168.1.1".
if (verbose_boot) {
static char line[sizeof("resolvconf: ") + sizeof(buf)];
int k = 0;
for (const char *p = "resolvconf: "; *p; p++) line[k++] = *p;
for (int j = 0; j < n; j++) line[k++] = buf[j];
write(1, line, k);
}
return 0;
}