fat: FAT12, FAT16 and FAT32 with long names, read and write
The filesystem every other system can read: a USB stick, an SD card, the EFI system partition. It is here first for the hardware report, which has to leave a machine on something that can be read on another one. mount -t vfat /dev/usb0p1 /mnt (msdos and fat work too; -o ro) Written from Microsoft's specification, and matched to Linux vfat where the specification leaves a choice: 8.3 names with the NT lowercase bits, long-name entries otherwise, Linux's alias rule (REPORTS, then BIGNUM~1), case-insensitive lookup, UTC times, the boot-sector dirty bit, every FAT copy and FSInfo kept up to date. It uses its own sector cache over the new blkdev_xfer, since a partition need not be a whole number of 4 KB blocks, and every operation that changes something reaches the disk before it returns. Inode numbers come from the entry position, or the first cluster for a directory, plus a small table for entries that moved. Around it: - /dev lists every disk and partition the block layer found (major 8, minor = block device), and raw reads and writes on them go by sector. - The ESP and MBR FAT partitions are registered, typed so the root probe passes over them. MBR entries with a bad status byte are skipped, so a superfloppy's boot code is not read as a partition table. - statfs, sync, fsync, umount (EBUSY while in use), shutdown, reboot and the power button all know about FAT. - create undoes and reports a failed ialloc or dirlink (ENOSPC, EINVAL, EPERM) instead of panicking; fs_ops gains refuse_types and no_hardlinks. - filewrite returns a short write instead of panicking on it. - utimensat is implemented. As a stub returning 0 it made toybox touch believe the file existed, so `touch newfile` created nothing on any filesystem. Tested in QEMU against mkfs.vfat images (FAT12 floppy, FAT16 on MBR, FAT32 superfloppy and GPT ESP) by writing, renaming, deleting, filling to ENOSPC and truncating there, then checking with fsck.fat -n and mtools on the host: all clean, contents match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
d90a72e6d3
commit
edb560de66
15 changed files with 2580 additions and 50 deletions
|
|
@ -21,7 +21,7 @@ struct blkdev {
|
|||
uint64 nsectors;
|
||||
uint32 secsize;
|
||||
int parent; // the disk's slot for a partition, -1 for a disk
|
||||
int schfs; // GPT says this is a SchFS volume
|
||||
int schfs; // 1: typed SchFS; -1: typed FAT; 0: anything else
|
||||
};
|
||||
|
||||
static struct blkdev devs[NBLKDEV];
|
||||
|
|
@ -134,8 +134,8 @@ readsec(struct blkdev *d, uint64 lba, uchar *buf)
|
|||
// 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 skipped. It is FAT, nothing here can mount it,
|
||||
// and registering it would only give the root probe one more thing to try.
|
||||
// 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)
|
||||
{
|
||||
|
|
@ -159,19 +159,19 @@ scan_gpt(int disk, uchar *sec)
|
|||
if (i % per == 0 && readsec(d, elba + i / per, sec) < 0)
|
||||
break;
|
||||
uchar *e = sec + (i % per) * esize;
|
||||
if (guid_zero(e) || guid_is(e, ESP_TYPE))
|
||||
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);
|
||||
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 ? ", SchFS" : "");
|
||||
schfs > 0 ? ", SchFS" : schfs < 0 ? ", EFI system" : "");
|
||||
found++;
|
||||
}
|
||||
}
|
||||
|
|
@ -180,6 +180,22 @@ scan_gpt(int disk, uchar *sec)
|
|||
|
||||
// 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)
|
||||
{
|
||||
|
|
@ -190,14 +206,15 @@ scan_mbr(int disk, uchar *sec)
|
|||
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, extended (which would need its own chain read), and
|
||||
// FAT/ESP types: none of them can be SchFS.
|
||||
if (type == 0 || type == 0xEE || type == 0x05 || type == 0x0F ||
|
||||
type == 0xEF || n == 0 || first + (uint64)n > d->nsectors)
|
||||
// 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, 0) >= 0)
|
||||
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);
|
||||
}
|
||||
|
|
@ -280,6 +297,28 @@ blkdev_rw(struct buf *b)
|
|||
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)
|
||||
{
|
||||
|
|
@ -348,7 +387,8 @@ blkdev_info(char *buf, int cap)
|
|||
off = put(buf, cap, off, " ");
|
||||
off = putu(buf, cap, off, (d->nsectors * d->secsize) >> 20);
|
||||
off = put(buf, cap, off, d->parent < 0 ? " disk" :
|
||||
d->schfs ? " schfs" : " part");
|
||||
d->schfs > 0 ? " schfs" :
|
||||
d->schfs < 0 ? " fat" : " part");
|
||||
off = put(buf, cap, off, "\n");
|
||||
}
|
||||
return off;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "buf.h"
|
||||
#include "file.h"
|
||||
#include "errno.h"
|
||||
#include "blkdev.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
|
||||
|
|
@ -38,11 +39,65 @@
|
|||
// 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.
|
||||
|
||||
// A disk or partition that registered with the block layer (minor 4 onward):
|
||||
// a SATA or NVMe disk, a USB stick, any partition on them. Moved through
|
||||
// blkdev_xfer in whole sectors rather than through the buffer cache, because a
|
||||
// partition need not be a whole number of BSIZE blocks and the buffer cache's
|
||||
// last block of one would run into the next. A read at the end returns 0, as
|
||||
// a file's does, and a write there fails with ENOSPC.
|
||||
static int
|
||||
blk_rw(uint dev, uint64 off, char *p, int n, int write)
|
||||
{
|
||||
uint32 ss = 0;
|
||||
uint64 nsec = blkdev_sectors(dev, &ss);
|
||||
if (nsec == 0)
|
||||
return -ENXIO;
|
||||
if (n <= 0)
|
||||
return 0;
|
||||
uint64 size = nsec * ss;
|
||||
if (off >= size)
|
||||
return write ? -ENOSPC : 0;
|
||||
if ((uint64)n > size - off)
|
||||
n = (int)(size - off);
|
||||
|
||||
char *tmp = kalloc();
|
||||
if (!tmp)
|
||||
return -ENOMEM;
|
||||
int done = 0;
|
||||
while (done < n) {
|
||||
uint64 pos = off + (uint64)done;
|
||||
uint64 lba = pos / ss;
|
||||
uint in = (uint)(pos % ss);
|
||||
uint32 cnt = (uint32)(BSIZE / ss);
|
||||
if (lba + cnt > nsec)
|
||||
cnt = (uint32)(nsec - lba);
|
||||
int want = (int)(cnt * ss) - (int)in;
|
||||
if (want > n - done)
|
||||
want = n - done;
|
||||
// Whole sectors are written as they are; a partial one is read first.
|
||||
uint32 rcnt = (in + want + ss - 1) / ss;
|
||||
if (!write || in || want % ss)
|
||||
if (blkdev_xfer(dev, lba, rcnt, tmp, 0) < 0)
|
||||
break;
|
||||
if (write) {
|
||||
memmove(tmp + in, p + done, want);
|
||||
if (blkdev_xfer(dev, lba, rcnt, tmp, 1) < 0)
|
||||
break;
|
||||
} else
|
||||
memmove(p + done, tmp + in, want);
|
||||
done += want;
|
||||
}
|
||||
kfree(tmp);
|
||||
return done ? done : -EIO;
|
||||
}
|
||||
|
||||
int
|
||||
diskread(struct inode *ip, uint64 off, char *dst, int n)
|
||||
{
|
||||
int dev = ip->minor;
|
||||
|
||||
if (dev >= BLKDEV_FIRST)
|
||||
return blk_rw((uint)dev, off, dst, n, 0);
|
||||
if (!ide_disk_present(dev))
|
||||
return -ENXIO;
|
||||
if (n <= 0)
|
||||
|
|
@ -71,6 +126,8 @@ diskwrite(struct inode *ip, uint64 off, char *src, int n)
|
|||
{
|
||||
int dev = ip->minor;
|
||||
|
||||
if (dev >= BLKDEV_FIRST)
|
||||
return blk_rw((uint)dev, off, src, n, 1);
|
||||
if (!ide_disk_present(dev))
|
||||
return -ENXIO;
|
||||
if (n <= 0)
|
||||
|
|
|
|||
Loading…
Reference in a new issue