Symlinks are followed in namex_from() now, in the middle of a path as well as at the end, and a relative target resolves from the directory that holds the link rather than from the caller's cwd. `ln -s f d1/s; cat d1/s` said "Symbolic link loop" because it looked for ./f. A loop is ELOOP after 40 links, a dangling link ENOENT, and open(O_CREAT) through a link to a file opens the file. divesymlink() is gone. mknod from a Linux program passes (mode, dev) where the native call passes (major, minor), and was read as the native one: `mknod n c 1 2` made node 432,258, and mkfifo made a character device. Both are decoded now; a FIFO is EPERM, and an existing name is EEXIST. /proc/mounts listed devtmpfs twice: once from a fixed line and once from the mount table. The fixed line is gone, and mount_add() refuses a filesystem mounted on its own root, which is where a second devtmpfs mount lands. A read-only FAT mount says ro. /proc/partitions exists, in Linux's format, and /proc/filesystems lists vfat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
407 lines
11 KiB
C
407 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// The block device registry, and the partition tables on what is in it.
|
|
// See include/blkdev.h.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "fs.h"
|
|
#include "buf.h"
|
|
#include "blkdev.h"
|
|
|
|
struct blkdev {
|
|
int used;
|
|
char name[16];
|
|
const struct blkdev_ops *ops;
|
|
void *priv;
|
|
uint64 start; // first sector, in the disk's own sectors
|
|
uint64 nsectors;
|
|
uint32 secsize;
|
|
int parent; // the disk's slot for a partition, -1 for a disk
|
|
int schfs; // 1: typed SchFS; -1: typed FAT; 0: anything else
|
|
};
|
|
|
|
static struct blkdev devs[NBLKDEV];
|
|
static struct spinlock blklock;
|
|
static int blkinit;
|
|
|
|
static struct blkdev *
|
|
lookup(uint dev)
|
|
{
|
|
if (dev < BLKDEV_FIRST || dev >= BLKDEV_FIRST + NBLKDEV)
|
|
return 0;
|
|
struct blkdev *d = &devs[dev - BLKDEV_FIRST];
|
|
return d->used ? d : 0;
|
|
}
|
|
|
|
static int
|
|
add(const char *name, const struct blkdev_ops *ops, void *priv,
|
|
uint64 start, uint64 nsectors, uint32 secsize, int parent, int schfs)
|
|
{
|
|
if (!blkinit) {
|
|
initlock(&blklock, "blkdev");
|
|
blkinit = 1;
|
|
}
|
|
acquire(&blklock);
|
|
for (int i = 0; i < NBLKDEV; i++) {
|
|
if (devs[i].used)
|
|
continue;
|
|
struct blkdev *d = &devs[i];
|
|
safestrcpy(d->name, name, sizeof(d->name));
|
|
d->ops = ops;
|
|
d->priv = priv;
|
|
d->start = start;
|
|
d->nsectors = nsectors;
|
|
d->secsize = secsize;
|
|
d->parent = parent;
|
|
d->schfs = schfs;
|
|
__sync_synchronize();
|
|
d->used = 1;
|
|
release(&blklock);
|
|
return BLKDEV_FIRST + i;
|
|
}
|
|
release(&blklock);
|
|
cprintf("blkdev: no slot for %s; it will not be usable\n", name);
|
|
return -1;
|
|
}
|
|
|
|
// --- partition tables -------------------------------------------------------
|
|
|
|
static uint32 le32(const uchar *p) { return p[0] | p[1] << 8 | p[2] << 16 | (uint32)p[3] << 24; }
|
|
static uint64 le64(const uchar *p) { return le32(p) | (uint64)le32(p + 4) << 32; }
|
|
|
|
// A GUID as GPT stores it: the first three fields little-endian, the last two
|
|
// as bytes. Compared against the printed form, so the constant in blkdev.h is
|
|
// the one a person types into sgdisk.
|
|
static int
|
|
guid_is(const uchar *g, const char *text)
|
|
{
|
|
static const int order[16] = { 3,2,1,0, 5,4, 7,6, 8,9, 10,11,12,13,14,15 };
|
|
uchar want[16];
|
|
int n = 0;
|
|
|
|
for (const char *p = text; *p && n < 32; p++) {
|
|
int v;
|
|
if (*p == '-')
|
|
continue;
|
|
if (*p >= '0' && *p <= '9') v = *p - '0';
|
|
else if (*p >= 'a' && *p <= 'f') v = *p - 'a' + 10;
|
|
else if (*p >= 'A' && *p <= 'F') v = *p - 'A' + 10;
|
|
else return 0;
|
|
if (n & 1) want[n / 2] |= v; else want[n / 2] = v << 4;
|
|
n++;
|
|
}
|
|
for (int i = 0; i < 16; i++)
|
|
if (g[order[i]] != want[i])
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
guid_zero(const uchar *g)
|
|
{
|
|
for (int i = 0; i < 16; i++)
|
|
if (g[i])
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
partname(char *out, const char *disk, int n)
|
|
{
|
|
int i = 0;
|
|
for (; disk[i] && i < 12; i++)
|
|
out[i] = disk[i];
|
|
out[i++] = 'p';
|
|
if (n >= 10)
|
|
out[i++] = '0' + n / 10;
|
|
out[i++] = '0' + n % 10;
|
|
out[i] = 0;
|
|
}
|
|
|
|
static int
|
|
readsec(struct blkdev *d, uint64 lba, uchar *buf)
|
|
{
|
|
return d->ops->rw(d->priv, lba, 1, buf, 0);
|
|
}
|
|
|
|
#define ESP_TYPE "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
|
|
|
// The GUID partition table: the header in sector 1, and its entries wherever
|
|
// it says. Only the primary copy is read -- a disk whose primary is damaged
|
|
// gets reported as having no partitions rather than half of some.
|
|
//
|
|
// The EFI system partition is registered like any other, as FAT: the root
|
|
// probe passes over it (see blkdev_is_schfs), and kernel/fs/fat mounts it.
|
|
static int
|
|
scan_gpt(int disk, uchar *sec)
|
|
{
|
|
struct blkdev *d = &devs[disk];
|
|
int found = 0;
|
|
|
|
if (readsec(d, 1, sec) < 0 || memcmp(sec, "EFI PART", 8) != 0)
|
|
return -1;
|
|
|
|
uint64 elba = le64(sec + 72);
|
|
uint32 count = le32(sec + 80);
|
|
uint32 esize = le32(sec + 84);
|
|
if (esize < 128 || esize > d->secsize || (esize & (esize - 1)) ||
|
|
count > 256 || elba < 2 || elba >= d->nsectors) {
|
|
cprintf("blkdev: %s: GPT header makes no sense; ignoring it\n", d->name);
|
|
return -1;
|
|
}
|
|
|
|
uint32 per = d->secsize / esize;
|
|
for (uint32 i = 0; i < count; i++) {
|
|
if (i % per == 0 && readsec(d, elba + i / per, sec) < 0)
|
|
break;
|
|
uchar *e = sec + (i % per) * esize;
|
|
if (guid_zero(e))
|
|
continue;
|
|
uint64 first = le64(e + 32), last = le64(e + 40);
|
|
if (first == 0 || last < first || last >= d->nsectors)
|
|
continue;
|
|
char name[16];
|
|
partname(name, d->name, i + 1);
|
|
int schfs = guid_is(e, SCHFS_GPT_TYPE) ? 1 : guid_is(e, ESP_TYPE) ? -1 : 0;
|
|
if (add(name, d->ops, d->priv, first, last - first + 1, d->secsize,
|
|
disk, schfs) >= 0) {
|
|
kinfo("blkdev: %s: %d MB%s\n", name,
|
|
(int)(((last - first + 1) * d->secsize) >> 20),
|
|
schfs > 0 ? ", SchFS" : schfs < 0 ? ", EFI system" : "");
|
|
found++;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
// The PC partition table, for disks that have one instead. Primary entries
|
|
// only; a protective entry (0xEE) means the disk is GPT and was read above.
|
|
//
|
|
// A FAT volume with no partition table -- a "superfloppy", which is how a
|
|
// good many USB sticks leave the factory -- also ends in 55 AA, and its boot
|
|
// code would read as four nonsense entries. Linux tells them apart by the
|
|
// status byte, which in a real entry is 0x00 or 0x80 and in boot code is
|
|
// whatever the code happens to be.
|
|
static int
|
|
fat_type(uint32 type)
|
|
{
|
|
switch (type) {
|
|
case 0x01: case 0x04: case 0x06: case 0x0B: case 0x0C: case 0x0E: case 0xEF:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
scan_mbr(int disk, uchar *sec)
|
|
{
|
|
struct blkdev *d = &devs[disk];
|
|
|
|
if (readsec(d, 0, sec) < 0 || sec[510] != 0x55 || sec[511] != 0xAA)
|
|
return;
|
|
for (int i = 0; i < 4; i++) {
|
|
uchar *e = sec + 446 + i * 16;
|
|
uint32 type = e[4], first = le32(e + 8), n = le32(e + 12);
|
|
// Empty, protective, and extended (which would need its own chain read).
|
|
if ((e[0] != 0x00 && e[0] != 0x80) ||
|
|
type == 0 || type == 0xEE || type == 0x05 || type == 0x0F ||
|
|
n == 0 || first + (uint64)n > d->nsectors)
|
|
continue;
|
|
char name[16];
|
|
partname(name, d->name, i + 1);
|
|
if (add(name, d->ops, d->priv, first, n, d->secsize, disk,
|
|
fat_type(type) ? -1 : 0) >= 0)
|
|
kinfo("blkdev: %s: %d MB, MBR type %x\n", name,
|
|
(int)(((uint64)n * d->secsize) >> 20), type);
|
|
}
|
|
}
|
|
|
|
int
|
|
blkdev_register(const char *name, const struct blkdev_ops *ops, void *priv,
|
|
uint64 nsectors, uint32 secsize)
|
|
{
|
|
if (secsize == 0 || secsize > BSIZE || BSIZE % secsize)
|
|
return -1;
|
|
int dev = add(name, ops, priv, 0, nsectors, secsize, -1, 0);
|
|
if (dev < 0)
|
|
return -1;
|
|
|
|
uchar *sec = (uchar *)kalloc();
|
|
if (sec) {
|
|
if (scan_gpt(dev - BLKDEV_FIRST, sec) < 0)
|
|
scan_mbr(dev - BLKDEV_FIRST, sec);
|
|
kfree((char *)sec);
|
|
}
|
|
return dev;
|
|
}
|
|
|
|
void
|
|
blkdev_unregister(int dev)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
if (!d)
|
|
return;
|
|
int disk = dev - BLKDEV_FIRST;
|
|
|
|
acquire(&blklock);
|
|
for (int i = 0; i < NBLKDEV; i++) {
|
|
if (devs[i].used && (i == disk || devs[i].parent == disk)) {
|
|
devs[i].used = 0;
|
|
kinfo("blkdev: %s removed\n", devs[i].name);
|
|
release(&blklock);
|
|
binval(BLKDEV_FIRST + i);
|
|
acquire(&blklock);
|
|
}
|
|
}
|
|
release(&blklock);
|
|
}
|
|
|
|
// --- I/O ---------------------------------------------------------------------
|
|
|
|
int
|
|
blkdev_present(uint dev)
|
|
{
|
|
return lookup(dev) != 0;
|
|
}
|
|
|
|
// Same contract as iderw(): fill b->data and set B_VALID on a read, write it
|
|
// out and clear B_DIRTY on a write. A failed transfer panics, for the reason
|
|
// usb_storage_rw() gives: bread() has no way to report one, and a buffer that
|
|
// silently holds the wrong bytes is how a filesystem is corrupted.
|
|
void
|
|
blkdev_rw(struct buf *b)
|
|
{
|
|
struct blkdev *d = lookup(b->dev);
|
|
int write = (b->flags & B_DIRTY) != 0;
|
|
|
|
if (!holdingsleep(&b->lock))
|
|
panic("blkdev_rw: buf not locked");
|
|
if (!d)
|
|
panic("blkdev_rw: no such device");
|
|
|
|
uint32 per = BSIZE / d->secsize;
|
|
uint64 lba = b->blockno * per;
|
|
if (lba + per > d->nsectors)
|
|
panic("blkdev_rw: block out of range");
|
|
|
|
if (d->ops->rw(d->priv, d->start + lba, per, b->data, write) < 0) {
|
|
cprintf("blkdev: %s: %s of block %d failed\n", d->name,
|
|
write ? "write" : "read", (int)b->blockno);
|
|
panic("blkdev_rw: transfer failed");
|
|
}
|
|
b->flags |= B_VALID;
|
|
b->flags &= ~B_DIRTY;
|
|
}
|
|
|
|
int
|
|
blkdev_xfer(uint dev, uint64 lba, uint32 count, void *buf, int write)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
|
|
if (!d || count == 0 || (uint64)count * d->secsize > BSIZE ||
|
|
lba + count > d->nsectors || lba + count < lba)
|
|
return -1;
|
|
return d->ops->rw(d->priv, d->start + lba, count, buf, write) < 0 ? -1 : 0;
|
|
}
|
|
|
|
uint64
|
|
blkdev_sectors(uint dev, uint32 *secsize)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
if (!d)
|
|
return 0;
|
|
if (secsize)
|
|
*secsize = d->secsize;
|
|
return d->nsectors;
|
|
}
|
|
|
|
int
|
|
blkdev_flush(uint dev)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
if (!d || !d->ops->flush)
|
|
return 0;
|
|
return d->ops->flush(d->priv);
|
|
}
|
|
|
|
int
|
|
blkdev_nth(int i)
|
|
{
|
|
for (int s = 0; s < NBLKDEV; s++)
|
|
if (devs[s].used && i-- == 0)
|
|
return BLKDEV_FIRST + s;
|
|
return -1;
|
|
}
|
|
|
|
const char *
|
|
blkdev_name(uint dev)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
return d ? d->name : "?";
|
|
}
|
|
|
|
int
|
|
blkdev_is_schfs(uint dev)
|
|
{
|
|
struct blkdev *d = lookup(dev);
|
|
return d ? d->schfs : 0;
|
|
}
|
|
|
|
// --- /proc/partitions --------------------------------------------------------
|
|
|
|
static int
|
|
put(char *buf, int cap, int off, const char *s)
|
|
{
|
|
while (*s && off < cap)
|
|
buf[off++] = *s++;
|
|
return off;
|
|
}
|
|
|
|
static int
|
|
putu(char *buf, int cap, int off, uint64 v)
|
|
{
|
|
char t[24];
|
|
int n = 0;
|
|
do { t[n++] = '0' + v % 10; v /= 10; } while (v);
|
|
while (n && off < cap)
|
|
buf[off++] = t[--n];
|
|
return off;
|
|
}
|
|
|
|
// putu(), right-aligned in `w` columns, the way printf("%*llu") would.
|
|
static int
|
|
putw(char *buf, int cap, int off, uint64 v, int w)
|
|
{
|
|
int n = 1;
|
|
for (uint64 t = v; t >= 10; t /= 10)
|
|
n++;
|
|
for (; w > n; w--)
|
|
off = put(buf, cap, off, " ");
|
|
return putu(buf, cap, off, v);
|
|
}
|
|
|
|
// /proc/partitions, in Linux's format to the column, because that is what
|
|
// reads it: lsblk's fallback, fdisk -l, every installer and rescue script.
|
|
// Sizes are in 1 KB blocks. The major is 8 for all of them, the one devtmpfs
|
|
// gives every disk here, and the minor is the device number, as in /dev.
|
|
int
|
|
blkdev_info(char *buf, int cap)
|
|
{
|
|
int off = put(buf, cap, 0, "major minor #blocks name\n\n");
|
|
for (int s = 0; s < NBLKDEV; s++) {
|
|
struct blkdev *d = &devs[s];
|
|
if (!d->used)
|
|
continue;
|
|
off = putw(buf, cap, off, 8, 4);
|
|
off = putw(buf, cap, off, BLKDEV_FIRST + s, 8);
|
|
off = putw(buf, cap, off, (d->nsectors * d->secsize) >> 10, 11);
|
|
off = put(buf, cap, off, " ");
|
|
off = put(buf, cap, off, d->name);
|
|
off = put(buf, cap, off, "\n");
|
|
}
|
|
return off;
|
|
}
|