Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
189 lines
6.1 KiB
C
189 lines
6.1 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.
|
|
if (verbose_boot) {
|
|
puts_("resolvconf: ");
|
|
write(1, buf, n);
|
|
}
|
|
return 0;
|
|
}
|