dev: raw disks are 64-bit, so a real drive stops wrapping at 4 GB

struct file already carried a uint64 offset and bread already took a
uint64 block number. The narrowing was one boundary in the middle:
devsw's read and write took a 32-bit uint, so fileread passed a 64-bit
f->off into a 32-bit parameter and the top half was discarded.

The effect was not a refusal at 4 GB but silent wraparound past it. A
read at 5 GB returned the bytes at 1 GB, reporting success. disk.c had a
DISK_MAX_BYTES constant documenting the limit, and nothing anywhere
referenced it -- the cap was described but never enforced, which is the
worst of the three options.

The signature is now uint64 across all nine files that carry it, and
diskread/diskwrite no longer narrow the block number on the way to
bread. Widening this is a prerequisite for NVMe and for any real disk:
namespaces start well above 4 GB and the failure mode reads valid data
from the wrong place.

Verified end to end rather than by inspection. A 6 GB sparse image with
"PAST4GB-MARKER-OK" written at byte 5368709120, attached as /dev/hdb to a
ramdisk-root kernel so device 1 was free:

  # dd if=/dev/hdb bs=4096 skip=1310720 count=1 | od -c | head -2
  0000000   P   A   S   T   4   G   B   -   M   A   R   K   E   R   -   O

Under the old code that offset truncates to 1 GB, where the image is
zeroes. The whole path is exercised: f->off, devsw, diskread, bread and
ide.c's LBA48.

Worth recording for next time: `make ramroot` stages the ESP itself, and
running `make esp` afterwards rebuilds the kernel without RAMROOT and
silently undoes it. The tell is a 855 KB kernel where a 25 MB one is
expected.

110 [ok], no failures, on the default build.
This commit is contained in:
Nexoniarz 2026-08-11 12:15:38 +02:00
commit ad2b321d56
9 changed files with 42 additions and 41 deletions

View file

@ -8,33 +8,33 @@
#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);
extern int consoleread(struct inode*, uint64, char*, int);
extern int consolewrite(struct inode*, uint64, char*, int);
extern int mouseread(struct inode*, uint64, char*, int);
static uint64 rseed = 0x2A99554433221100ULL;
int
nullwrite(struct inode *ip, uint off, char *dst, int n)
nullwrite(struct inode *ip, uint64 off, char *dst, int n)
{
return n;
}
int
nullread(struct inode *ip, uint off, char *dst, int n)
nullread(struct inode *ip, uint64 off, char *dst, int n)
{
return 0;
}
int
zeroread(struct inode *ip, uint off, char *dst, int n)
zeroread(struct inode *ip, uint64 off, char *dst, int n)
{
memset(dst, 0, n);
return n;
}
int
randomread(struct inode *ip, uint off, char *dst, int n)
randomread(struct inode *ip, uint64 off, char *dst, int n)
{
for(int i = 0; i < n; i++){
rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL;
@ -46,19 +46,19 @@ randomread(struct inode *ip, uint off, char *dst, int 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)
fullwrite(struct inode *ip, uint64 off, char *dst, int n)
{
return -ENOSPC;
}
int
mousewrite(struct inode *ip, uint off, char *dst, int n)
mousewrite(struct inode *ip, uint64 off, char *dst, int n)
{
return n;
}
int
memread(struct inode *ip, uint off, char *dst, int n)
memread(struct inode *ip, uint64 off, char *dst, int n)
{
switch(ip->minor) {
case 1: return consoleread(ip, off, dst, n);
@ -73,7 +73,7 @@ memread(struct inode *ip, uint off, char *dst, int n)
}
int
memwrite(struct inode *ip, uint off, char *dst, int n)
memwrite(struct inode *ip, uint64 off, char *dst, int n)
{
switch(ip->minor) {
case 1: return consolewrite(ip, off, dst, n);

View file

@ -31,14 +31,15 @@
#include "file.h"
#include "errno.h"
// A byte offset arrives as a 32-bit `uint` from the devsw signature, so a raw
// device is addressable to 4 GB. Larger disks need the signature widened --
// worth doing before anyone installs onto a real one, and called out here
// rather than discovered as silent wraparound at the 4 GB mark.
#define DISK_MAX_BYTES 0xFFFFFFFFULL
// The byte offset is 64-bit, all the way from struct file's off through the
// devsw signature to bread's block number. It used to narrow to a 32-bit uint
// at the devsw boundary, which capped a raw device at 4 GB -- and the constant
// that documented the cap was never referenced by anything, so the failure was
// not a refusal at 4 GB but silent wraparound past it. Nothing here may narrow
// it again: an NVMe namespace or any modern disk is far beyond that.
int
diskread(struct inode *ip, uint off, char *dst, int n)
diskread(struct inode *ip, uint64 off, char *dst, int n)
{
int dev = ip->minor;
@ -49,8 +50,8 @@ diskread(struct inode *ip, uint off, char *dst, int n)
int done = 0;
while (done < n) {
uint64 pos = (uint64)off + (uint64)done;
uint blk = (uint)(pos / BSIZE);
uint64 pos = off + (uint64)done;
uint64 blk = pos / BSIZE;
uint inblk = (uint)(pos % BSIZE);
int want = BSIZE - (int)inblk;
if (want > n - done)
@ -66,7 +67,7 @@ diskread(struct inode *ip, uint off, char *dst, int n)
}
int
diskwrite(struct inode *ip, uint off, char *src, int n)
diskwrite(struct inode *ip, uint64 off, char *src, int n)
{
int dev = ip->minor;
@ -77,8 +78,8 @@ diskwrite(struct inode *ip, uint off, char *src, int n)
int done = 0;
while (done < n) {
uint64 pos = (uint64)off + (uint64)done;
uint blk = (uint)(pos / BSIZE);
uint64 pos = off + (uint64)done;
uint64 blk = pos / BSIZE;
uint inblk = (uint)(pos % BSIZE);
int want = BSIZE - (int)inblk;
if (want > n - done)

View file

@ -64,7 +64,7 @@ pixel_format(struct fb_var_screeninfo *v, unsigned int bpp)
}
int
fbread(struct inode *ip, uint off, char *dst, int n)
fbread(struct inode *ip, uint64 off, char *dst, int n)
{
volatile uchar *fb = vga_fb_ptr();
uint64 size = vga_fb_size();
@ -98,7 +98,7 @@ fb_mapped(void)
}
int
fbwrite(struct inode *ip, uint off, char *src, int n)
fbwrite(struct inode *ip, uint64 off, char *src, int n)
{
volatile uchar *fb = vga_fb_ptr();
uint64 size = vga_fb_size();

View file

@ -233,7 +233,7 @@ input_ioctl(struct inode *ip, int request, addr_t arg)
// desynchronise the reader permanently -- every later event would be split
// across two reads and reassembled wrong.
int
inputread(struct inode *ip, uint off, char *dst, int n)
inputread(struct inode *ip, uint64 off, char *dst, int n)
{
struct input_dev *d;
int got = 0;
@ -266,7 +266,7 @@ inputread(struct inode *ip, uint off, char *dst, int n)
// evdev accepts writes to set keyboard LEDs. There are no LEDs to set here,
// and a program that writes one should not see an error for it.
int
inputwrite(struct inode *ip, uint off, char *src, int n)
inputwrite(struct inode *ip, uint64 off, char *src, int n)
{
return n;
}

View file

@ -197,7 +197,7 @@ uint8 get_mouse_buttons(void) {
}
int
mouseread(struct inode *ip, uint off, char *dst, int n)
mouseread(struct inode *ip, uint64 off, char *dst, int n)
{
struct mouse_data m;
int size = sizeof(struct mouse_data);

View file

@ -346,7 +346,7 @@ proc_gen_global(int id, char *buf, int cap)
}
int
procread(struct inode *ip, uint off, char *dst, int n)
procread(struct inode *ip, uint64 off, char *dst, int n)
{
char buf[PROC_BUFSZ];
int len = proc_gen_global(ip->minor, buf, PROC_BUFSZ);
@ -422,7 +422,7 @@ proc_write_sysrq(int user_src, uint64 src, uint n)
// rather than through the /proc filesystem. `src` is already a kernel buffer
// here, which is why it passes user_src = 0.
int
procwrite(struct inode *ip, uint off, char *src, int n)
procwrite(struct inode *ip, uint64 off, char *src, int n)
{
(void)off;