SchOS/cmd/resolvconf/main.c
Nexoniarz 617a4aec0a boot: one line when it works, all of it when it does not
A boot printed about thirty lines before reaching a prompt. Every one was
useful once, and printed on every boot for ever they train the person in front
of the machine to stop reading their own console -- which is expensive on the
day one of them changes.

    SchOS UEFI loader
    Press F8 for boot options... booting

    Welcome to SchOS!  (F8 at boot for the full log, or dmesg once you are in)

    root@schos / »

**Nothing was deleted, it was routed.** consputc() already wrote to the klog
ring before anywhere else, so a line can be kept in full while being held back
from the two places a person looks. kinfo() is cprintf() with that routing, and
`dmesg` still has the whole boot with timestamps afterwards. The rule for which
is which is not how interesting a line is -- everything was interesting to
somebody once -- but whether you would want to see it on a machine that is
*working*. `pci: 7 devices` is yes the first time and no every time after;
`e1000: no DMA memory for the rings` is yes for ever and stays a cprintf.

The verbose boot the F8 menu already offered now means something much bigger,
which is the other half of this: it is the only way to see any of it live.

Userspace follows through /proc/bootmode, one word. schinit, schinitd and vgpud
hold back their progress lines and never their failures -- a supervisor that
goes quiet when a job dies is worse than one that chatters.

Two things found by testing rather than by reading:

  - schinit read /proc/bootmode *before* bootstrap() mounted /proc, so it got
    "quiet" every time and the verbose boot was quiet in the one process that
    runs first. It reads after the mount now.
  - resolvconf announced "already exists; -f to replace it" on every boot after
    the first, which is a line about having done nothing. It is quiet when the
    file already says what it would have written, and speaks only when the two
    disagree -- which means the nameserver changed and the machine is using the
    old one.

Verified both ways: quiet boot is six lines, a forced-verbose build prints all
thirty plus the `verbose boot:` steps, and /proc/kmsg holds every line in both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-14 21:19:50 +02:00

161 lines
5.2 KiB
C

// SPDX-License-Identifier: Apache-2.0
//
// 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 "types.h"
#include "user.h"
#include "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);
}
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;
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 the first time, because it is the moment names start
// working and somebody may be waiting to know it.
puts_("resolvconf: ");
write(1, buf, n);
return 0;
}