// 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" // 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 int diskread(struct inode *ip, uint 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 = (uint64)off + (uint64)done; uint blk = (uint)(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, uint 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 = (uint64)off + (uint64)done; uint blk = (uint)(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; }