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:
parent
f4107177ea
commit
ad2b321d56
9 changed files with 42 additions and 41 deletions
|
|
@ -198,8 +198,8 @@ int schfs_kreaddir_dev(uint dev, uint64 dir,
|
||||||
uint uptime(void);
|
uint uptime(void);
|
||||||
|
|
||||||
// procfs.c
|
// procfs.c
|
||||||
int procread(struct inode*, uint, char*, int);
|
int procread(struct inode*, uint64, char*, int);
|
||||||
int procwrite(struct inode*, uint, char*, int);
|
int procwrite(struct inode*, uint64, char*, int);
|
||||||
// Shared with the /proc filesystem (kernel/fs/procfs.c): one owns the
|
// Shared with the /proc filesystem (kernel/fs/procfs.c): one owns the
|
||||||
// namespace, the other the content.
|
// namespace, the other the content.
|
||||||
int proc_gen_global(int id, char *buf, int cap);
|
int proc_gen_global(int id, char *buf, int cap);
|
||||||
|
|
@ -208,8 +208,8 @@ void procfs_mount(void);
|
||||||
|
|
||||||
// input.c -- raw evdev event devices
|
// input.c -- raw evdev event devices
|
||||||
void inputinit(void);
|
void inputinit(void);
|
||||||
int inputread(struct inode*, uint, char*, int);
|
int inputread(struct inode*, uint64, char*, int);
|
||||||
int inputwrite(struct inode*, uint, char*, int);
|
int inputwrite(struct inode*, uint64, char*, int);
|
||||||
int inputready(struct inode*);
|
int inputready(struct inode*);
|
||||||
void inputclose(struct inode*);
|
void inputclose(struct inode*);
|
||||||
void inputopen(struct inode*);
|
void inputopen(struct inode*);
|
||||||
|
|
@ -431,13 +431,13 @@ void fbclose(struct inode*);
|
||||||
// Called from sys_mmap: a mapping of /dev/fb0 is a licence to draw with no
|
// Called from sys_mmap: a mapping of /dev/fb0 is a licence to draw with no
|
||||||
// further syscalls, so it is the last the kernel hears about it.
|
// further syscalls, so it is the last the kernel hears about it.
|
||||||
void fb_mapped(void);
|
void fb_mapped(void);
|
||||||
int fbread(struct inode*, uint, char*, int);
|
int fbread(struct inode*, uint64, char*, int);
|
||||||
int fbwrite(struct inode*, uint, char*, int);
|
int fbwrite(struct inode*, uint64, char*, int);
|
||||||
int fb_ioctl(int request, addr_t arg);
|
int fb_ioctl(int request, addr_t arg);
|
||||||
// dev/disk.c -- /dev/hdN, raw seekable access to a whole disk. The minor is the
|
// dev/disk.c -- /dev/hdN, raw seekable access to a whole disk. The minor is the
|
||||||
// IDE device number. Writes bypass every filesystem above them.
|
// IDE device number. Writes bypass every filesystem above them.
|
||||||
int diskread(struct inode*, uint, char*, int);
|
int diskread(struct inode*, uint64, char*, int);
|
||||||
int diskwrite(struct inode*, uint, char*, int);
|
int diskwrite(struct inode*, uint64, char*, int);
|
||||||
// drivers/net/e1000 -- raw Ethernet. No protocol stack above it yet, so these
|
// drivers/net/e1000 -- raw Ethernet. No protocol stack above it yet, so these
|
||||||
// are the whole interface: queue a frame, take a frame, and the MAC.
|
// are the whole interface: queue a frame, take a frame, and the MAC.
|
||||||
int e1000_transmit(const void *frame, int len);
|
int e1000_transmit(const void *frame, int len);
|
||||||
|
|
|
||||||
|
|
@ -123,8 +123,8 @@ struct inode {
|
||||||
// table mapping major device number to
|
// table mapping major device number to
|
||||||
// device functions
|
// device functions
|
||||||
struct devsw {
|
struct devsw {
|
||||||
int (*read)(struct inode*, uint, char*, int);
|
int (*read)(struct inode*, uint64, char*, int);
|
||||||
int (*write)(struct inode*, uint, char*, int);
|
int (*write)(struct inode*, uint64, char*, int);
|
||||||
// Optional. Returns 0 when a read would block, which is the only thing
|
// Optional. Returns 0 when a read would block, which is the only thing
|
||||||
// fileread needs to know to honour O_NONBLOCK without every device driver
|
// fileread needs to know to honour O_NONBLOCK without every device driver
|
||||||
// growing a flags argument. A device that omits this is treated as always
|
// growing a flags argument. A device that omits this is treated as always
|
||||||
|
|
|
||||||
|
|
@ -655,7 +655,7 @@ consoleintr(int (*getc)(void))
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
consoleread(struct inode *ip, uint off, char *dst, int n)
|
consoleread(struct inode *ip, uint64 off, char *dst, int n)
|
||||||
{
|
{
|
||||||
uint target;
|
uint target;
|
||||||
int c;
|
int c;
|
||||||
|
|
@ -729,7 +729,7 @@ console_input_ready(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
consolewrite(struct inode *ip, uint off, char *buf, int n)
|
consolewrite(struct inode *ip, uint64 off, char *buf, int n)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,33 +8,33 @@
|
||||||
#include "x86.h"
|
#include "x86.h"
|
||||||
#include "errno.h"
|
#include "errno.h"
|
||||||
|
|
||||||
extern int consoleread(struct inode*, uint, char*, int);
|
extern int consoleread(struct inode*, uint64, char*, int);
|
||||||
extern int consolewrite(struct inode*, uint, char*, int);
|
extern int consolewrite(struct inode*, uint64, char*, int);
|
||||||
extern int mouseread(struct inode*, uint, char*, int);
|
extern int mouseread(struct inode*, uint64, char*, int);
|
||||||
|
|
||||||
static uint64 rseed = 0x2A99554433221100ULL;
|
static uint64 rseed = 0x2A99554433221100ULL;
|
||||||
|
|
||||||
int
|
int
|
||||||
nullwrite(struct inode *ip, uint off, char *dst, int n)
|
nullwrite(struct inode *ip, uint64 off, char *dst, int n)
|
||||||
{
|
{
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
nullread(struct inode *ip, uint off, char *dst, int n)
|
nullread(struct inode *ip, uint64 off, char *dst, int n)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
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);
|
memset(dst, 0, n);
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
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++){
|
for(int i = 0; i < n; i++){
|
||||||
rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL;
|
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
|
// /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.
|
// ENOSPC. Programs use it to exercise their out-of-disk-space paths.
|
||||||
int
|
int
|
||||||
fullwrite(struct inode *ip, uint off, char *dst, int n)
|
fullwrite(struct inode *ip, uint64 off, char *dst, int n)
|
||||||
{
|
{
|
||||||
return -ENOSPC;
|
return -ENOSPC;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
mousewrite(struct inode *ip, uint off, char *dst, int n)
|
mousewrite(struct inode *ip, uint64 off, char *dst, int n)
|
||||||
{
|
{
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
memread(struct inode *ip, uint off, char *dst, int n)
|
memread(struct inode *ip, uint64 off, char *dst, int n)
|
||||||
{
|
{
|
||||||
switch(ip->minor) {
|
switch(ip->minor) {
|
||||||
case 1: return consoleread(ip, off, dst, n);
|
case 1: return consoleread(ip, off, dst, n);
|
||||||
|
|
@ -73,7 +73,7 @@ memread(struct inode *ip, uint off, char *dst, int n)
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
memwrite(struct inode *ip, uint off, char *dst, int n)
|
memwrite(struct inode *ip, uint64 off, char *dst, int n)
|
||||||
{
|
{
|
||||||
switch(ip->minor) {
|
switch(ip->minor) {
|
||||||
case 1: return consolewrite(ip, off, dst, n);
|
case 1: return consolewrite(ip, off, dst, n);
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,15 @@
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
#include "errno.h"
|
#include "errno.h"
|
||||||
|
|
||||||
// A byte offset arrives as a 32-bit `uint` from the devsw signature, so a raw
|
// The byte offset is 64-bit, all the way from struct file's off through the
|
||||||
// device is addressable to 4 GB. Larger disks need the signature widened --
|
// devsw signature to bread's block number. It used to narrow to a 32-bit uint
|
||||||
// worth doing before anyone installs onto a real one, and called out here
|
// at the devsw boundary, which capped a raw device at 4 GB -- and the constant
|
||||||
// rather than discovered as silent wraparound at the 4 GB mark.
|
// that documented the cap was never referenced by anything, so the failure was
|
||||||
#define DISK_MAX_BYTES 0xFFFFFFFFULL
|
// 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
|
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;
|
int dev = ip->minor;
|
||||||
|
|
||||||
|
|
@ -49,8 +50,8 @@ diskread(struct inode *ip, uint off, char *dst, int n)
|
||||||
|
|
||||||
int done = 0;
|
int done = 0;
|
||||||
while (done < n) {
|
while (done < n) {
|
||||||
uint64 pos = (uint64)off + (uint64)done;
|
uint64 pos = off + (uint64)done;
|
||||||
uint blk = (uint)(pos / BSIZE);
|
uint64 blk = pos / BSIZE;
|
||||||
uint inblk = (uint)(pos % BSIZE);
|
uint inblk = (uint)(pos % BSIZE);
|
||||||
int want = BSIZE - (int)inblk;
|
int want = BSIZE - (int)inblk;
|
||||||
if (want > n - done)
|
if (want > n - done)
|
||||||
|
|
@ -66,7 +67,7 @@ diskread(struct inode *ip, uint off, char *dst, int n)
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
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;
|
int dev = ip->minor;
|
||||||
|
|
||||||
|
|
@ -77,8 +78,8 @@ diskwrite(struct inode *ip, uint off, char *src, int n)
|
||||||
|
|
||||||
int done = 0;
|
int done = 0;
|
||||||
while (done < n) {
|
while (done < n) {
|
||||||
uint64 pos = (uint64)off + (uint64)done;
|
uint64 pos = off + (uint64)done;
|
||||||
uint blk = (uint)(pos / BSIZE);
|
uint64 blk = pos / BSIZE;
|
||||||
uint inblk = (uint)(pos % BSIZE);
|
uint inblk = (uint)(pos % BSIZE);
|
||||||
int want = BSIZE - (int)inblk;
|
int want = BSIZE - (int)inblk;
|
||||||
if (want > n - done)
|
if (want > n - done)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ pixel_format(struct fb_var_screeninfo *v, unsigned int bpp)
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
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();
|
volatile uchar *fb = vga_fb_ptr();
|
||||||
uint64 size = vga_fb_size();
|
uint64 size = vga_fb_size();
|
||||||
|
|
@ -98,7 +98,7 @@ fb_mapped(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
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();
|
volatile uchar *fb = vga_fb_ptr();
|
||||||
uint64 size = vga_fb_size();
|
uint64 size = vga_fb_size();
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ input_ioctl(struct inode *ip, int request, addr_t arg)
|
||||||
// desynchronise the reader permanently -- every later event would be split
|
// desynchronise the reader permanently -- every later event would be split
|
||||||
// across two reads and reassembled wrong.
|
// across two reads and reassembled wrong.
|
||||||
int
|
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;
|
struct input_dev *d;
|
||||||
int got = 0;
|
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,
|
// 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.
|
// and a program that writes one should not see an error for it.
|
||||||
int
|
int
|
||||||
inputwrite(struct inode *ip, uint off, char *src, int n)
|
inputwrite(struct inode *ip, uint64 off, char *src, int n)
|
||||||
{
|
{
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ uint8 get_mouse_buttons(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
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;
|
struct mouse_data m;
|
||||||
int size = sizeof(struct mouse_data);
|
int size = sizeof(struct mouse_data);
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,7 @@ proc_gen_global(int id, char *buf, int cap)
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
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];
|
char buf[PROC_BUFSZ];
|
||||||
int len = proc_gen_global(ip->minor, 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
|
// rather than through the /proc filesystem. `src` is already a kernel buffer
|
||||||
// here, which is why it passes user_src = 0.
|
// here, which is why it passes user_src = 0.
|
||||||
int
|
int
|
||||||
procwrite(struct inode *ip, uint off, char *src, int n)
|
procwrite(struct inode *ip, uint64 off, char *src, int n)
|
||||||
{
|
{
|
||||||
(void)off;
|
(void)off;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue