SchOS/include/blkdev.h
Nexoniarz edb560de66 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
2026-10-23 09:33:35 +02:00

73 lines
3.7 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// Block devices that are found rather than assumed.
//
// The first four device numbers are fixed and always were: IDE 0 and 1, the
// ramdisk, the first USB stick. Everything since -- a SATA disk behind AHCI,
// an NVMe namespace, and every partition on any of them -- registers here at
// probe time and is handed the next free number from BLKDEV_FIRST.
//
// A partition is a block device in its own right, with block 0 at its first
// sector. That is the whole of partition support from a filesystem's side:
// SchFS on /dev/sata0p2 sees a volume that starts at zero, exactly as it would
// on a whole disk, and needs to know nothing about GPT -- which is also what
// a Linux driver for SchFS will see when it is handed /dev/sda2.
#define BLKDEV_FIRST 4
#define NBLKDEV 16 // so device numbers stay below SCHFS_DEV (64)
// The GPT partition type of a SchFS volume, as it is written and printed:
// 47138FA9-B79C-47A6-9F7D-AC1F7F572D5E. sgdisk and fdisk accept it as is.
// Generated for SchFS on 2026-10-22; nothing else uses it.
#define SCHFS_GPT_TYPE "47138FA9-B79C-47A6-9F7D-AC1F7F572D5E"
// What a driver supplies. `rw` moves `count` of the device's own sectors at
// `lba` to or from `buf`, a kernel address; it is never asked for more than
// BSIZE bytes at once. Returns 0, or -1 if the device refused.
struct blkdev_ops {
int (*rw)(void *priv, uint64 lba, uint32 count, void *buf, int write);
int (*flush)(void *priv); // 0 when there is nothing to flush
};
// Register a whole disk. Its partition table, if it has one, is read here and
// every partition registered after it. Returns the disk's device number, or
// -1 if the table is full.
int blkdev_register(const char *name, const struct blkdev_ops *ops,
void *priv, uint64 nsectors, uint32 secsize);
// The disk has gone -- a USB stick pulled out. Removes it and every partition
// on it, and drops whatever the buffer cache still holds for them, so that a
// different disk given the same number later is not served the old one's
// blocks. A filesystem still mounted from it fails on its next access.
void blkdev_unregister(int dev);
void binval(uint dev); // kernel/dev/bio.c
int blkdev_present(uint dev);
void blkdev_rw(struct buf *b);
int blkdev_flush(uint dev);
// For the root probe: the registered devices in order, partitions after the
// disk they are on. Returns the device number of the i'th, or -1 past the end.
int blkdev_nth(int i);
const char *blkdev_name(uint dev);
// 1 for a partition typed SCHFS_GPT_TYPE, 0 for anything that might be SchFS,
// and -1 for one whose type says it cannot be -- an EFI system partition, an
// MBR FAT type. The root probe takes 1 and then 0, so -1 is never tried: a
// FAT partition is mounted as FAT (kernel/fs/fat), not offered as a root.
int blkdev_is_schfs(uint dev);
int blkdev_info(char *buf, int cap); // /proc/partitions
// Sectors straight to and from a filesystem's own buffer, for one that does
// not go through the buffer cache. kernel/fs/fat is: FAT addresses 512-byte
// sectors, a partition need not be a whole number of 4 KB blocks, and a
// cache block hanging over the end of one is a write into the next.
//
// `lba` counts the device's own sectors from the start of the partition, and
// the transfer is at most BSIZE bytes. Returns 0, or -1 for a transfer out of
// range or one the device refused -- it does not panic, because a USB stick
// can leave in the middle of one.
int blkdev_xfer(uint dev, uint64 lba, uint32 count, void *buf, int write);
// Size in sectors and sector size; 0 if there is no such device.
uint64 blkdev_sectors(uint dev, uint32 *secsize);