root: find the filesystem instead of being told at build time, and fix dmesg
`#define ROOTDEV 1` -- IDE disk 1 -- so a kernel could only ever root on the
disk it was *built* expecting. Booting the same image from a USB stick was
impossible by construction, and that was the one thing standing between this
kernel and its own acceptance test, which begins "boot on real hardware from a
USB stick".
rootdev_mount() probes. schfs_kmount() was already written for exactly this --
its own comment says "the uberblock scan doubles as the probe: no valid
uberblock means this is simply not a SchFS volume, which is not an error worth
reporting loudly" -- and a failed probe leaves the slot unmounted and reusable,
which is what makes a loop over candidates legal at all. So the change is one
function and one call site; the only real use of ROOTDEV was `schfs_kmount()`.
Order is preference, not confidence: a RAMDISK_ROOT build wins outright, then
USB, then IDE 1, then IDE 0. USB before the fixed disks because a stick that is
plugged in was plugged in on purpose. iderw() panics on an absent drive rather
than failing, so each candidate is checked with ide_disk_present() /
usb_storage_present() first -- which is what those predicates are for.
Verified with **no IDE disk attached at all**: boots, mounts the stick, writes
a file, and the file is still there on the next boot.
[ 0.000021] root: USB disk (device 3)
And when nothing matches, it names what it looked at, because "no root
filesystem" with no list is the least useful thing a kernel can say at the one
moment somebody needs to know which disk it expected:
root: no SchFS volume on any candidate device. Looked at:
device 3, USB disk: not present
device 1, IDE disk 1: not present
device 0, IDE disk 0: present, but no SchFS on it
**That message is also how dmesg turned out to be broken**, because it did not
appear in `dmesg` and should have. `dmesg` printed *nothing at all*, on a
system whose boot banner says "or dmesg once you are in" and whose entire
quiet-boot design is "route the detail into the kernel log and say how to read
it". The log was there the whole time -- `cat /proc/kmsg` has always worked --
but the one command anybody would reach for could not see it.
Two separate faults, each alone enough:
- **sys_syslog() was `return 0`.** dmesg does not read /proc/kmsg; it calls
klogctl(10) for the buffer size and klogctl(3) to read. 0 to the first one
means "the log is zero bytes", and a stub that answers 0 is
indistinguishable from a machine that logged nothing. Implemented for the
actions that mean something here; the rest return -EINVAL rather than 0,
because a console log level this kernel does not have is not a request that
succeeded.
- **With that fixed, dmesg was still empty.** It parses every line with
`sscanf(msg, "<%u>[%llu.%llu] %n", ...)` and silently drops anything that
does not match. The ring's `[ 0.00] text` already matches everything
after the priority -- scanf skips the leading spaces -- so the only missing
thing was the `<6>`. klog_render_syslog() adds it on the way out rather
than storing it: the ring is also what a person reads with `cat
/proc/kmsg` and what PSOGM points at after a crash, and an ABI detail
belongs at the boundary that has the ABI.
Also: the first version of the no-root diagnostic printed
%-11s device 1596400
because cprintf() has no field-width specifier -- it prints "%-11s" literally
and then reads the next argument for the %d. Plain %s now, with the note.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fdbd9bcf95
commit
7e88cea151
6 changed files with 307 additions and 21 deletions
|
|
@ -296,3 +296,95 @@ klog_read(uint64 off, char *dst, int n)
|
|||
|
||||
return n;
|
||||
}
|
||||
|
||||
// Render the ring as syslog(2) records, for dmesg.
|
||||
//
|
||||
// The ring holds plain readable text -- `[ 0.00] e820: ...` -- because it
|
||||
// is also what a person reads with `cat /proc/kmsg`, and what PSOGM points at
|
||||
// after a crash. syslog(2)'s callers expect Linux's record format, which is
|
||||
// the same line with a priority in front:
|
||||
//
|
||||
// <6>[ 0.00] e820: 1017 MB usable in 8 regions
|
||||
//
|
||||
// toybox's dmesg parses each line with
|
||||
//
|
||||
// sscanf(msg, "<%u>[%llu.%llu] %n", ...) != 3 -> return
|
||||
//
|
||||
// and *silently drops* anything that does not match. So a working syslog(2)
|
||||
// handing back the ring verbatim produced an empty dmesg with no error: every
|
||||
// line was parsed, rejected and discarded. Only the `<6>` was missing -- the
|
||||
// bracketed timestamp already matches, because scanf skips the leading spaces.
|
||||
//
|
||||
// Rendering here rather than storing the prefix in the ring: the write path
|
||||
// runs in panic context and from every cprintf, and the stored form is read by
|
||||
// humans. An ABI detail belongs at the boundary that has the ABI, which is the
|
||||
// same rule sigset_from_user() follows one directory over.
|
||||
//
|
||||
// <6> is KERN_INFO. There are no per-message levels to derive one from, and
|
||||
// inventing severity from the text would be a worse lie than a constant.
|
||||
int
|
||||
klog_render_syslog(char *dst, int cap)
|
||||
{
|
||||
uint64 have = klog_len(), off = 0;
|
||||
int out = 0, atline = 1;
|
||||
char chunk[128];
|
||||
|
||||
if (!klog || cap <= 0 || have == 0)
|
||||
return 0;
|
||||
|
||||
// Linux returns the *most recent* `cap` bytes. The prefix makes the output
|
||||
// longer than the source, so find the first whole line that lets the rest
|
||||
// fit: count what a render from `off` would cost, and drop lines from the
|
||||
// front until it does. Whole lines, because half a record is worse to a
|
||||
// parser than a missing one.
|
||||
for (;;) {
|
||||
uint64 scan = off;
|
||||
int need = 0, nl = 1; // nl: next byte starts a line
|
||||
while (scan < have) {
|
||||
int got = klog_read(scan, chunk, sizeof(chunk));
|
||||
if (got <= 0)
|
||||
break;
|
||||
for (int i = 0; i < got; i++) {
|
||||
if (nl) { need += 3; nl = 0; }
|
||||
need++;
|
||||
if (chunk[i] == '\n') nl = 1;
|
||||
}
|
||||
scan += got;
|
||||
}
|
||||
if (need <= cap || off >= have)
|
||||
break;
|
||||
// Advance past one line.
|
||||
uint64 p = off;
|
||||
int done = 0;
|
||||
while (p < have && !done) {
|
||||
int got = klog_read(p, chunk, sizeof(chunk));
|
||||
if (got <= 0) { off = have; break; }
|
||||
for (int i = 0; i < got; i++) {
|
||||
p++;
|
||||
if (chunk[i] == '\n') { off = p; done = 1; break; }
|
||||
}
|
||||
if (!done) off = p;
|
||||
}
|
||||
if (!done && off >= have)
|
||||
break;
|
||||
}
|
||||
|
||||
while (off < have && out < cap) {
|
||||
int got = klog_read(off, chunk, sizeof(chunk));
|
||||
if (got <= 0)
|
||||
break;
|
||||
for (int i = 0; i < got && out < cap; i++) {
|
||||
if (atline) {
|
||||
if (out + 3 > cap)
|
||||
return out; // never emit half a prefix
|
||||
dst[out++] = '<'; dst[out++] = '6'; dst[out++] = '>';
|
||||
atline = 0;
|
||||
}
|
||||
dst[out++] = chunk[i];
|
||||
if (chunk[i] == '\n')
|
||||
atline = 1;
|
||||
}
|
||||
off += got;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue