// Raw block devices: /dev/hda, /dev/hdb. // // Until this existed, nothing outside the kernel could touch a disk. sys_mount // does not even look at the device it is given -- it mounts the one SchFS // volume the kernel 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 exactly a program that needs all three. // // This is a character device in the devsw table rather than a separate block // layer, because that is where the offset already is: fileread/filewrite pass // f->off and advance it by whatever comes back, which is all a seekable device // needs. `dd if=/dev/hdb bs=4096 count=1` works, and so does lseek. // // The minor number is the IDE device number: 0 is the disk the kernel booted // from, 1 is the second channel. Same numbering iderw() uses, deliberately -- // two numbering schemes for the same disks is a bug waiting to be written. // // WARNING, and the reason this file is short: writes here bypass the // filesystem entirely. Writing to a device with a mounted filesystem on it will // corrupt that filesystem, because the buffer cache above still believes its // own copy. That is inherent to a raw device and not a defect -- but it is why // an installer must target a disk it has not mounted. #include "types.h" #include "defs.h" #include "param.h" #include "spinlock.h" #include "sleeplock.h" #include "fs.h" #include "buf.h" #include "file.h" #include "errno.h" // 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, uint64 off, char *dst, int n) { int dev = ip->minor; if (!ide_disk_present(dev)) return -ENXIO; if (n <= 0) return 0; int done = 0; while (done < n) { uint64 pos = off + (uint64)done; uint64 blk = pos / BSIZE; uint inblk = (uint)(pos % BSIZE); int want = BSIZE - (int)inblk; if (want > n - done) want = n - done; struct buf *b = bread((uint)dev, blk); memmove(dst + done, b->data + inblk, want); brelse(b); done += want; } return done; } int diskwrite(struct inode *ip, uint64 off, char *src, int n) { int dev = ip->minor; if (!ide_disk_present(dev)) return -ENXIO; if (n <= 0) return 0; int done = 0; while (done < n) { uint64 pos = off + (uint64)done; uint64 blk = pos / BSIZE; uint inblk = (uint)(pos % BSIZE); int want = BSIZE - (int)inblk; if (want > n - done) want = n - done; // Read-modify-write for anything that is not a whole block. bread() first // even when the whole block is being replaced would be a wasted revolution, // but distinguishing the two cases means trusting the caller's alignment; // the disk is not the bottleneck in an installer and correctness here is // worth more than the read. struct buf *b = bread((uint)dev, blk); memmove(b->data + inblk, src + done, want); bwrite(b); brelse(b); done += want; } return done; }