Nothing outside the kernel could touch a disk. sys_mount does not even look at the device it is handed -- it mounts the one SchFS volume found at boot and ignores `source` -- so a program had no way to read a partition table, format a volume or write a boot sector. An installer is a program that needs all three, so this is the first piece of it. A character device in devsw rather than a separate block layer, because that is where the offset already lives: fileread/filewrite pass f->off and advance it by what comes back, which is everything a seekable device needs. The minor is the IDE device number, the same numbering iderw() uses; two schemes for the same disks would be a bug waiting to happen. Writes bypass every filesystem above them, which is inherent to a raw device and why an installer must target a disk it has not mounted. The nodes are created unconditionally, like every other entry in /dev. That matters more than it looks: with no node, `echo x > /dev/hdb` silently creates an ordinary file of that name and appears to work. It did, and it agreed with itself on read-back; only the host's copy of the disk showed nothing had been written. st_rdev was never filled, so every node in /dev reported "0, 0" under ls -l. Harmless while nothing read it, wrong as soon as something has to tell two disks apart. Filled with the full Linux dev_t encoding rather than (major << 8 | minor), which only works while both stay small. Verified against the host as an independent oracle, both directions: the guest wrote to /dev/hdb and the host saw those bytes at offset 0; the host wrote at offset 4096 and the guest read them back with dd skip=. ls -l now shows 1,1 for console, 2,0 for fb0 and 8,1 for hdb. Known limit, commented in disk.c: devsw passes the offset as a 32-bit uint, so a raw disk is addressable to 4 GB. That must be widened before anyone installs onto a real disk. bootcheck 108 [ok] with -Fb -Tty -Usb -Sock -Evdev -Ps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
111 lines
3 KiB
C
111 lines
3 KiB
C
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "fs.h"
|
|
#include "file.h"
|
|
#include "x86.h"
|
|
#include "errno.h"
|
|
|
|
extern int consoleread(struct inode*, uint, char*, int);
|
|
extern int consolewrite(struct inode*, uint, char*, int);
|
|
extern int mouseread(struct inode*, uint, char*, int);
|
|
|
|
static uint64 rseed = 0x2A99554433221100ULL;
|
|
|
|
int
|
|
nullwrite(struct inode *ip, uint off, char *dst, int n)
|
|
{
|
|
return n;
|
|
}
|
|
|
|
int
|
|
nullread(struct inode *ip, uint off, char *dst, int n)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
zeroread(struct inode *ip, uint off, char *dst, int n)
|
|
{
|
|
memset(dst, 0, n);
|
|
return n;
|
|
}
|
|
|
|
int
|
|
randomread(struct inode *ip, uint off, char *dst, int n)
|
|
{
|
|
for(int i = 0; i < n; i++){
|
|
rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL;
|
|
dst[i] = (rseed >> 32) & 0xFF;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// /dev/full — reads as an endless run of zero bytes, every write fails with
|
|
// ENOSPC. Programs use it to exercise their out-of-disk-space paths.
|
|
int
|
|
fullwrite(struct inode *ip, uint off, char *dst, int n)
|
|
{
|
|
return -ENOSPC;
|
|
}
|
|
|
|
int
|
|
mousewrite(struct inode *ip, uint off, char *dst, int n)
|
|
{
|
|
return n;
|
|
}
|
|
|
|
int
|
|
memread(struct inode *ip, uint off, char *dst, int n)
|
|
{
|
|
switch(ip->minor) {
|
|
case 1: return consoleread(ip, off, dst, n);
|
|
case 2: return nullread(ip, off, dst, n);
|
|
case 3: return consoleread(ip, off, dst, n);
|
|
case 5: return zeroread(ip, off, dst, n);
|
|
case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros
|
|
case 8:
|
|
case 9: return randomread(ip, off, dst, n);
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
int
|
|
memwrite(struct inode *ip, uint off, char *dst, int n)
|
|
{
|
|
switch(ip->minor) {
|
|
case 1: return consolewrite(ip, off, dst, n);
|
|
case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC
|
|
case 2:
|
|
case 5:
|
|
case 8:
|
|
case 9: return nullwrite(ip, off, dst, n);
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
struct devsw devsw[] = {
|
|
[0] = { 0, 0 },
|
|
[1] = { memread, memwrite },
|
|
// /dev/fb0 (major 2) — the framebuffer, kernel/dev/fb.c. This used to be
|
|
// { nullread, nullwrite }, so writing pixels to it succeeded and did
|
|
// nothing.
|
|
// The close hook gives the text console its screen back when whatever was
|
|
// drawing on the framebuffer exits — see fbclose().
|
|
[2] = { fbread, fbwrite, 0, fbclose },
|
|
[3] = { mouseread, mousewrite },
|
|
[4] = { 0, 0 },
|
|
// /dev/tty (major 5) — alias for the console
|
|
[5] = { memread, memwrite },
|
|
// /proc status files (major 6) — see kernel/dev/procfs.c
|
|
[6] = { procread, procwrite },
|
|
// /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The
|
|
// minor is the device index, so event0 is the keyboard and event1 the mouse.
|
|
[7] = { inputread, inputwrite, inputready, inputclose, inputopen },
|
|
// /dev/hdN (major 8) — raw disks, kernel/dev/disk.c. The minor is the IDE
|
|
// device number. No `ready` hook: a disk is always ready, which is what
|
|
// fileread assumes when one is absent.
|
|
[8] = { diskread, diskwrite },
|
|
};
|