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:
Nexoniarz 2026-10-23 09:33:35 +02:00
commit edb560de66
15 changed files with 2580 additions and 50 deletions

View file

@ -63,6 +63,9 @@ KERN_FS = $(addprefix $(BUILD)/kernel/fs/, \
KERN_FS += $(addprefix $(BUILD)/kernel/fs/schfs/, \ KERN_FS += $(addprefix $(BUILD)/kernel/fs/schfs/, \
btree.o space.o vol.o obj.o objw.o attr.o kmount.o vfs.o) btree.o space.o vol.o obj.o objw.o attr.o kmount.o vfs.o)
# FAT12/16/32 with long names, for the disks every other system can read.
KERN_FS += $(BUILD)/kernel/fs/fat/fat.o
# Sockets. Unix domain only for now; an IP stack becomes more files here # Sockets. Unix domain only for now; an IP stack becomes more files here
# rather than a reshaping of what is already written. # rather than a reshaping of what is already written.
KERN_NET = $(addprefix $(BUILD)/kernel/net/, \ KERN_NET = $(addprefix $(BUILD)/kernel/net/, \
@ -926,7 +929,7 @@ $(BUILD)/kernel/drivers/%.o: kernel/drivers/%.c
$(CC) $(CFLAGS) -I$(dir $<) -c $< -o $@ $(CC) $(CFLAGS) -I$(dir $<) -c $< -o $@
$(BUILD)/kernel/fs/%.o: kernel/fs/%.c $(BUILD)/kernel/fs/%.o: kernel/fs/%.c
@mkdir -p $(BUILD)/kernel/fs @mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@
$(BUILD)/kernel/fs/schfs/%.o: kernel/fs/schfs/%.c $(BUILD)/kernel/fs/schfs/%.o: kernel/fs/schfs/%.c

105
docs/architecture/fat.md Normal file
View file

@ -0,0 +1,105 @@
# FAT
FAT12, FAT16 and FAT32 with VFAT long names, read and write.
`kernel/fs/fat/fat.c`, behind the same `fs_ops` vtable as SchFS.
It exists for one reason: it is the filesystem every other system can read. A
USB stick written here opens on Linux, Windows and macOS, and so does the EFI
system partition. The first user is the hardware report, which has to leave a
machine that may have no network on something you can carry to another one.
```
mount -t vfat /dev/usb0p1 /mnt # msdos and fat are accepted as well
mount -t vfat -o ro /dev/sata0p1 /boot/efi
umount /mnt
```
The disks and partitions the block layer found are in `/dev` under the names
it gave them (`usb0`, `usb0p1`, `sata0p2`, `nvme0p1`), generated like the rest
of devtmpfs: a stick appears when it is plugged in and goes when it is pulled.
---
## Written from the specification, matched to Linux
The on-disk rules come from Microsoft's FAT specification (1.03). Where the
specification leaves a choice, the choice is Linux's vfat, so that a volume
written here and one written by Linux look alike:
- **The FAT type is decided by the cluster count and nothing else**: under 4085
is FAT12, under 65525 FAT16, otherwise FAT32. The "FAT32" label in the boot
sector is a comment.
- **Short names.** A name that fits 8.3 in one case (`readme.txt`, `README.TXT`)
is stored as a short entry alone, with the lowercase bits in byte 12 that
Windows NT and Linux use. A name that does not (`ReadMe.txt`, `Reports`,
`big numbers.log`) gets long-name entries plus an alias: its uppercase self
when that loses nothing (`REPORTS`), otherwise `BIGNUM~1.LOG`, counting up
until it is unique.
- **Lookups ignore case** (ASCII and Latin-1), and a name with a long entry
still answers to its alias, so `PROGRA~1` works.
- **Times are stored as UTC.** FAT has no time zone, and that is what Linux
assumes when it is not told one.
- **The dirty bit.** Set in the boot sector while a volume is mounted
read-write, cleared on unmount, sync-on-shutdown and reboot. A volume found
with it set is reported (`fsck.fat` reads the same bit).
- **Every FAT copy is written**, unless the volume has mirroring turned off, in
which case only the active one is. FSInfo's free count and next-free hint are
read and kept up to date.
- **Refused, with Linux's errno**: device nodes, symlinks and sockets (`EPERM`,
through `fs_ops.refuse_types`), hard links (`EPERM`, `fs_ops.no_hardlinks`),
and names containing `" * / : < > ? \ |` (`EINVAL`).
Checked against `mkfs.vfat`-made FAT12, FAT16 (MBR) and FAT32 (superfloppy and
GPT ESP) volumes: written here, then `fsck.fat -n` and mtools on the host.
## Inodes, on a filesystem that has none
A FAT file *is* its directory entry: name, first cluster, size and times are all
in those 32 bytes. The inode number says where to find it:
| object | number |
|--------|--------|
| the root | `1` |
| a directory | `2 << 40` \| its first cluster |
| a file | `1 << 40` \| byte position of its short entry / 32 |
| a file created here, or one whose number is taken | `3 << 40` \| a counter |
A directory is numbered by its cluster because `..` must be able to find it,
and `..` holds the parent's cluster and nothing about where the parent's entry
is. A file is numbered by its entry, as Linux does it.
The VFS renames by linking the new name to the same number and then unlinking
the old one, so a file's entry can move while its number stays. A small
per-volume table records every number whose entry is not where the number says.
It is consulted first in both directions, and anything nobody holds can be
dropped from it, since the next lookup by name finds the entry afresh.
## I/O
Through a cache of its own (64 lines of 4 KB per volume), not the buffer cache.
FAT addresses sectors, a partition need not be a whole number of 4 KB blocks,
and the buffer cache's last block of one would read and write into the next
partition. `blkdev_xfer` takes sectors and refuses a transfer past the end
rather than panicking.
**Every operation that changes something writes its dirty lines back before it
returns.** A stick pulled out between two commands holds both of them. `sync`
and `fsync` also flush the drive's own cache. Unmount, shutdown, reboot and the
power button sync and mark the volume clean.
A new cluster is zeroed in the cache before use. FAT has no holes, and a
directory ends at the first entry whose first byte is zero.
## What it does not do
- **Names longer than 29 bytes.** FAT allows 255 UTF-16 units, but the VFS's
`DIRSIZ` caps a path component, so a longer name is cut short on the way in.
Lifting that is a VFS change.
- **Permissions and owners.** Every file shows as `0755 root:root`, like Linux
vfat with its defaults. The read-only attribute is kept but not enforced.
- **exFAT.** Large SD cards (SDXC, over 32 GB) ship with it. It is a different
filesystem, not a FAT variant.
- **Code pages.** Short names are read as CP437 and written as ASCII; anything
else goes in a long name.
- **Directories don't move**, which is the VFS's rule for every filesystem.
- **No `fsck`.** A volume found dirty is reported, not repaired.

View file

@ -30,11 +30,28 @@ struct fs_ops {
struct inode *(*ialloc)(uint dev, short type); struct inode *(*ialloc)(uint dev, short type);
int (*getdents)(struct inode *dp, char *out, int cap, uint64 *off); int (*getdents)(struct inode *dp, char *out, int cap, uint64 *off);
uint max_write; // largest write in one call; 0 for no limit
uint refuse_types; // 1 << T_* the filesystem cannot store: EPERM
int no_hardlinks; // link() fails with EPERM
}; };
``` ```
Two implementations exist: `schfs_ops` (`kernel/fs/schfs/vfs.c`) and `procfs_ops` The implementations: `schfs_ops` (`kernel/fs/schfs/vfs.c`), `fat_ops`
(`kernel/fs/procfs.c`). (`kernel/fs/fat/fat.c`, see [fat.md](fat.md)), `procfs_ops`
(`kernel/fs/procfs.c`) and `devfs_ops` (`kernel/fs/devfs.c`).
**Creation can fail, and is undone when it does.** `ialloc` and `dirlink` used
to panic on failure, because on v1 and SchFS they cannot fail for a reason the
caller could have avoided. FAT can: the volume fills, the fixed root directory
of FAT16 holds 512 names, a name holds a character FAT does not allow. So
`createat_x()` gives the object back and reports `ENOSPC`, `EINVAL`, `EPERM` or
`EROFS`, and `open`, `mkdir`, `mknod` and `symlink` return that errno instead of
`ENOENT`.
**A short write is a full disk.** `filewrite()` panicked on one, which held as
long as no filesystem ever wrote short. Now `write(2)` returns what went in, and
the next call gets the error.
`poff` is deliberately **an opaque cookie** rather than a byte offset — `poff` is deliberately **an opaque cookie** rather than a byte offset —
meaningful only to the same filesystem's `dirunlink`. A directory does not have meaningful only to the same filesystem's `dirunlink`. A directory does not have
@ -102,6 +119,10 @@ during path resolution. `/proc` is mounted by init:
mount("proc", "/proc", "proc", 0, 0); mount("proc", "/proc", "proc", 0, 0);
``` ```
The filesystem types are `proc`, `devtmpfs`, `schfs` and `vfat` (also accepted
as `msdos` and `fat`). Unmounting a FAT volume while anything on it is open, or
is some process's working directory, fails with `EBUSY`.
## procfs ## procfs
`kernel/fs/procfs.c`. A real filesystem behind the same vtable, not a set of `kernel/fs/procfs.c`. A real filesystem behind the same vtable, not a set of
@ -165,7 +186,14 @@ A node exists because the kernel has the device, or it does not exist.
`console`, `null`, `keyboard`, `zero`, `full`, `random`, `urandom`, `tty`, `console`, `null`, `keyboard`, `zero`, `full`, `random`, `urandom`, `tty`,
`stdin`, `stdout`, `stderr`, `fb0`, `mouse`, `hda`, `hdb`, and `input/event0` `stdin`, `stdout`, `stderr`, `fb0`, `mouse`, `hda`, `hdb`, and `input/event0`
and `input/event1` in one subdirectory. Minors under major 1 follow Linux's and `input/event1` in one subdirectory.
Every disk and partition the block layer registered is there too, under the
name it was given (`sata0`, `sata0p2`, `nvme0p1`, `usb0p1`): major 8, minor its
block device number, listed only while it is present. Reads and writes go
through `blkdev_xfer` in whole sectors, end at the end of the device, and do
not pass through the buffer cache. A partition need not be a whole number of
4 KB blocks, and the buffer cache's last block of one would run into the next. Minors under major 1 follow Linux's
mem-major layout where it costs nothing. mem-major layout where it costs nothing.
The raw disks are created whether or not a drive is present: opening one with The raw disks are created whether or not a drive is present: opening one with

View file

@ -51,6 +51,23 @@ int blkdev_flush(uint dev);
// disk they are on. Returns the device number of the i'th, or -1 past the end. // disk they are on. Returns the device number of the i'th, or -1 past the end.
int blkdev_nth(int i); int blkdev_nth(int i);
const char *blkdev_name(uint dev); const char *blkdev_name(uint dev);
int blkdev_is_schfs(uint dev); // a partition typed SCHFS_GPT_TYPE // 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 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);

30
include/fat.h Normal file
View file

@ -0,0 +1,30 @@
#pragma once
// FAT12, FAT16 and FAT32, with VFAT long names: the filesystem every other
// operating system can read, which is the whole reason to have it. A USB
// stick, an EFI system partition, an SD card. See kernel/fs/fat/fat.c.
//
// Mounted the way Linux mounts it -- `mount -t vfat /dev/usb0p1 /mnt` -- and
// "msdos" and "fat" are accepted as the same thing.
// Filesystem device numbers for mounted FAT volumes. Like SCHFS_DEV, runtime
// identities for the inode cache that nothing on disk records.
#define FAT_VOL_SLOTS 4
#define FAT_DEV 104
#define FAT_IS_DEV(d) ((d) >= FAT_DEV && (d) < FAT_DEV + FAT_VOL_SLOTS)
// The root directory's inode number on every FAT volume.
#define FAT_ROOT_INO 1
// Mount the FAT volume on block device `bdev`. Returns its filesystem device
// number, or a negative errno: -ENODEV for no FAT there, -EBUSY for no free
// slot or already mounted.
int fat_kmount(uint bdev, int readonly);
// Flush it, mark it cleanly unmounted, and let it go.
int fat_kumount(uint dev);
int fat_readonly(uint dev);
// Write out everything dirty on every mounted volume. `final` also marks each
// one clean, for a shutdown that will not get to unmount them.
void fat_ksync_all(int final);
// Cluster size, clusters and free clusters, for statfs. -1 if not FAT.
int fat_kstatfs(uint dev, uint64 *bsize, uint64 *total, uint64 *freec);

View file

@ -82,6 +82,14 @@ struct fs_ops {
// handed, so chopping a 1 MB write into 6 KB pieces bought 170 commits and // handed, so chopping a 1 MB write into 6 KB pieces bought 170 commits and
// the flushes that go with them for nothing. // the flushes that go with them for nothing.
uint max_write; uint max_write;
// Object types this filesystem has no way to store, as a mask of 1 << T_*.
// Creating one fails with EPERM, which is what Linux answers for mknod or a
// symlink on vfat. 0 for "anything".
uint refuse_types;
// One name per file: link() fails with EPERM. Rename still works -- it is
// a second name for as long as it takes to remove the first.
int no_hardlinks;
}; };
// In-memory inode. // In-memory inode.
@ -161,6 +169,13 @@ struct inode {
// The synthetic /dev. See kernel/fs/devfs.c. // The synthetic /dev. See kernel/fs/devfs.c.
#define DEVFS_DEV 81 #define DEVFS_DEV 81
// Mounted FAT volumes, FAT_DEV onward. See include/fat.h.
// 1 if some inode on dev with this number is referenced. In fs.c.
int iheld(uint dev, uint64 inum);
// 1 if any inode on dev but `except` is referenced (0: no exception). In fs.c.
int ibusy(uint dev, uint64 except);
// table mapping major device number to // table mapping major device number to
// device functions // device functions
struct devsw { struct devsw {

View file

@ -24,6 +24,7 @@
#include "pci.h" #include "pci.h"
#include "irq.h" #include "irq.h"
#include "acpi.h" #include "acpi.h"
#include "fat.h"
#include <uacpi/uacpi.h> #include <uacpi/uacpi.h>
#include <uacpi/utilities.h> #include <uacpi/utilities.h>
@ -87,6 +88,7 @@ power_off_work(uacpi_handle ctx)
(void)ctx; (void)ctx;
cprintf("acpi: power button pressed; syncing and powering off\n"); cprintf("acpi: power button pressed; syncing and powering off\n");
schfs_ksync_all(); schfs_ksync_all();
fat_ksync_all(1);
acpi_poweroff(); acpi_poweroff();
cprintf("acpi: power-off failed; it is safe to switch off now\n"); cprintf("acpi: power-off failed; it is safe to switch off now\n");
} }

View file

@ -21,7 +21,7 @@ struct blkdev {
uint64 nsectors; uint64 nsectors;
uint32 secsize; uint32 secsize;
int parent; // the disk's slot for a partition, -1 for a disk 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]; 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 // 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. // 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, // The EFI system partition is registered like any other, as FAT: the root
// and registering it would only give the root probe one more thing to try. // probe passes over it (see blkdev_is_schfs), and kernel/fs/fat mounts it.
static int static int
scan_gpt(int disk, uchar *sec) 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) if (i % per == 0 && readsec(d, elba + i / per, sec) < 0)
break; break;
uchar *e = sec + (i % per) * esize; uchar *e = sec + (i % per) * esize;
if (guid_zero(e) || guid_is(e, ESP_TYPE)) if (guid_zero(e))
continue; continue;
uint64 first = le64(e + 32), last = le64(e + 40); uint64 first = le64(e + 32), last = le64(e + 40);
if (first == 0 || last < first || last >= d->nsectors) if (first == 0 || last < first || last >= d->nsectors)
continue; continue;
char name[16]; char name[16];
partname(name, d->name, i + 1); 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, if (add(name, d->ops, d->priv, first, last - first + 1, d->secsize,
disk, schfs) >= 0) { disk, schfs) >= 0) {
kinfo("blkdev: %s: %d MB%s\n", name, kinfo("blkdev: %s: %d MB%s\n", name,
(int)(((last - first + 1) * d->secsize) >> 20), (int)(((last - first + 1) * d->secsize) >> 20),
schfs ? ", SchFS" : ""); schfs > 0 ? ", SchFS" : schfs < 0 ? ", EFI system" : "");
found++; found++;
} }
} }
@ -180,6 +180,22 @@ scan_gpt(int disk, uchar *sec)
// The PC partition table, for disks that have one instead. Primary entries // 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. // 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 static void
scan_mbr(int disk, uchar *sec) scan_mbr(int disk, uchar *sec)
{ {
@ -190,14 +206,15 @@ scan_mbr(int disk, uchar *sec)
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
uchar *e = sec + 446 + i * 16; uchar *e = sec + 446 + i * 16;
uint32 type = e[4], first = le32(e + 8), n = le32(e + 12); uint32 type = e[4], first = le32(e + 8), n = le32(e + 12);
// Empty, protective, extended (which would need its own chain read), and // Empty, protective, and extended (which would need its own chain read).
// FAT/ESP types: none of them can be SchFS. if ((e[0] != 0x00 && e[0] != 0x80) ||
if (type == 0 || type == 0xEE || type == 0x05 || type == 0x0F || type == 0 || type == 0xEE || type == 0x05 || type == 0x0F ||
type == 0xEF || n == 0 || first + (uint64)n > d->nsectors) n == 0 || first + (uint64)n > d->nsectors)
continue; continue;
char name[16]; char name[16];
partname(name, d->name, i + 1); 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, kinfo("blkdev: %s: %d MB, MBR type %x\n", name,
(int)(((uint64)n * d->secsize) >> 20), type); (int)(((uint64)n * d->secsize) >> 20), type);
} }
@ -280,6 +297,28 @@ blkdev_rw(struct buf *b)
b->flags &= ~B_DIRTY; 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 int
blkdev_flush(uint dev) blkdev_flush(uint dev)
{ {
@ -348,7 +387,8 @@ blkdev_info(char *buf, int cap)
off = put(buf, cap, off, " "); off = put(buf, cap, off, " ");
off = putu(buf, cap, off, (d->nsectors * d->secsize) >> 20); off = putu(buf, cap, off, (d->nsectors * d->secsize) >> 20);
off = put(buf, cap, off, d->parent < 0 ? " disk" : 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"); off = put(buf, cap, off, "\n");
} }
return off; return off;

View file

@ -30,6 +30,7 @@
#include "buf.h" #include "buf.h"
#include "file.h" #include "file.h"
#include "errno.h" #include "errno.h"
#include "blkdev.h"
// The byte offset is 64-bit, all the way from struct file's off through the // 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 // 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 // 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. // 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 int
diskread(struct inode *ip, uint64 off, char *dst, int n) diskread(struct inode *ip, uint64 off, char *dst, int n)
{ {
int dev = ip->minor; int dev = ip->minor;
if (dev >= BLKDEV_FIRST)
return blk_rw((uint)dev, off, dst, n, 0);
if (!ide_disk_present(dev)) if (!ide_disk_present(dev))
return -ENXIO; return -ENXIO;
if (n <= 0) if (n <= 0)
@ -71,6 +126,8 @@ diskwrite(struct inode *ip, uint64 off, char *src, int n)
{ {
int dev = ip->minor; int dev = ip->minor;
if (dev >= BLKDEV_FIRST)
return blk_rw((uint)dev, off, src, n, 1);
if (!ide_disk_present(dev)) if (!ide_disk_present(dev))
return -ENXIO; return -ENXIO;
if (n <= 0) if (n <= 0)

View file

@ -37,12 +37,18 @@
#include "fs.h" #include "fs.h"
#include "file.h" #include "file.h"
#include "errno.h" #include "errno.h"
#include "blkdev.h"
// Inode numbering. Two directories and a flat table of nodes, so the number is // Inode numbering. Two directories and a flat table of nodes, so the number is
// either a directory id or an index into devnodes[] offset past them. // either a directory id or an index into devnodes[] offset past them.
#define DEVFS_ROOT 1ULL #define DEVFS_ROOT 1ULL
#define DEVFS_INPUT 2ULL /* the /dev/input directory */ #define DEVFS_INPUT 2ULL /* the /dev/input directory */
#define DEVFS_FILE0 16ULL /* devnodes[0] is inode 16 */ #define DEVFS_FILE0 16ULL /* devnodes[0] is inode 16 */
// Disks and partitions the block layer found: /dev/sata0, /dev/sata0p1,
// /dev/usb0p1, named as it named them. Major 8 like hda, minor the block
// device number, and inode DEVFS_BLK0 plus that number. Not in devnodes[],
// because they come and go: a stick plugged in appears, pulled out vanishes.
#define DEVFS_BLK0 4096ULL
// Which directory a node lives in. Only two exist and a third would want a real // Which directory a node lives in. Only two exist and a third would want a real
// tree rather than a flag, which is the point at which this file should grow // tree rather than a flag, which is the point at which this file should grow
@ -140,6 +146,12 @@ devfs_iload(struct inode *ip)
ip->nlink = 1; ip->nlink = 1;
ip->major = devnodes[i].major; ip->major = devnodes[i].major;
ip->minor = devnodes[i].minor; ip->minor = devnodes[i].minor;
} else if (ip->inum >= DEVFS_BLK0 &&
blkdev_present((uint)(ip->inum - DEVFS_BLK0))) {
ip->type = T_DEV;
ip->nlink = 1;
ip->major = 8;
ip->minor = (short)(ip->inum - DEVFS_BLK0);
} else { } else {
ip->type = 0; /* no such node */ ip->type = 0; /* no such node */
ip->nlink = 0; ip->nlink = 0;
@ -185,6 +197,11 @@ devfs_dirlookup(struct inode *dp, char *name, uint *poff)
if (devnodes[i].dir == want && streq(name, devnodes[i].name)) if (devnodes[i].dir == want && streq(name, devnodes[i].name))
return iget(DEVFS_DEV, DEVFS_FILE0 + (uint64)i); return iget(DEVFS_DEV, DEVFS_FILE0 + (uint64)i);
if (want == D_ROOT)
for (int k = 0, b; (b = blkdev_nth(k)) >= 0; k++)
if (streq(name, blkdev_name((uint)b)))
return iget(DEVFS_DEV, DEVFS_BLK0 + (uint64)b);
return 0; return 0;
} }
@ -270,6 +287,16 @@ devfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
base++; base++;
} }
if (want == D_ROOT)
for (int k = 0, b; (b = blkdev_nth(k)) >= 0; k++) {
if (n > base) { base++; continue; }
if (emit(out, cap, &used, DEVFS_BLK0 + (uint64)b, 6 /* DT_BLK */,
blkdev_name((uint)b)) < 0)
goto out;
n++;
base++;
}
out: out:
*poff = n; *poff = n;
return used; return used;

1996
kernel/fs/fat/fat.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -276,11 +276,16 @@ filewrite(struct file *f, char *addr, int n)
if(r < 0) if(r < 0)
break; break;
if(r != n1)
panic("short filewrite");
i += r; i += r;
// Short means full. v1 and SchFS never wrote short, which is why this
// was a panic; FAT does, when the volume or its root directory fills.
// write(2) reports what went in, and the next call gets the error.
if(r != n1)
break;
} }
return i == n ? n : -1; if(i > 0)
return i;
return r < 0 ? r : -1;
} }
panic("filewrite"); panic("filewrite");
} }

View file

@ -43,6 +43,7 @@
#include "buf.h" #include "buf.h"
#include "fsstat.h" #include "fsstat.h"
#include "file.h" #include "file.h"
#include "fat.h"
extern uint uptime(void); extern uint uptime(void);
@ -277,6 +278,7 @@ static const struct fs_ops v1_ops = {
// Defined in fs/schfs/vfs.c, which is only linked when SchFS is built in. // Defined in fs/schfs/vfs.c, which is only linked when SchFS is built in.
extern const struct fs_ops schfs_ops; extern const struct fs_ops schfs_ops;
extern const struct fs_ops fat_ops;
// Defined in fs/procfs.c -- the synthetic /proc. // Defined in fs/procfs.c -- the synthetic /proc.
extern const struct fs_ops procfs_ops; extern const struct fs_ops procfs_ops;
extern const struct fs_ops devfs_ops; extern const struct fs_ops devfs_ops;
@ -290,7 +292,7 @@ extern const struct fs_ops devfs_ops;
int int
fs_readonly(uint dev) fs_readonly(uint dev)
{ {
return SCHFS_IS_SNAP_DEV(dev); return SCHFS_IS_SNAP_DEV(dev) || (FAT_IS_DEV(dev) && fat_readonly(dev));
} }
static const struct fs_ops * static const struct fs_ops *
@ -302,6 +304,7 @@ ops_for(uint dev)
if (SCHFS_IS_ANY_DEV(dev)) return &schfs_ops; if (SCHFS_IS_ANY_DEV(dev)) return &schfs_ops;
if (dev == PROCFS_DEV) return &procfs_ops; if (dev == PROCFS_DEV) return &procfs_ops;
if (dev == DEVFS_DEV) return &devfs_ops; if (dev == DEVFS_DEV) return &devfs_ops;
if (FAT_IS_DEV(dev)) return &fat_ops;
return &v1_ops; return &v1_ops;
} }
@ -468,6 +471,38 @@ iget(uint dev, uint64 inum)
return ip; return ip;
} }
// Is (dev, inum) in the inode cache with a reference? For a filesystem that
// keeps its own table of inode numbers and wants to know which it can drop.
int
iheld(uint dev, uint64 inum)
{
int held = 0;
acquire(&itable.lock);
for (struct inode *ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++)
if (ip->ref > 0 && ip->dev == dev && ip->inum == inum) {
held = 1;
break;
}
release(&itable.lock);
return held;
}
// Is anything on dev held, other than `except` (a mounted root, which the
// mount itself holds)? For umount.
int
ibusy(uint dev, uint64 except)
{
int busy = 0;
acquire(&itable.lock);
for (struct inode *ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++)
if (ip->ref > 0 && ip->dev == dev && ip->inum != except) {
busy = 1;
break;
}
release(&itable.lock);
return busy;
}
struct inode * struct inode *
idup(struct inode *ip) idup(struct inode *ip)
{ {

View file

@ -20,6 +20,7 @@
#include "pciuser.h" #include "pciuser.h"
#include "keymap.h" /* KB_*LAYOUT — the keyboard layout catalogue */ #include "keymap.h" /* KB_*LAYOUT — the keyboard layout catalogue */
#include "netuser.h" #include "netuser.h"
#include "fat.h"
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Linux-ABI-compatible struct stat (x86_64 "struct stat" from // Linux-ABI-compatible struct stat (x86_64 "struct stat" from
@ -173,23 +174,25 @@ at_nameiparent(int dirfd, char *path, char *name)
static struct inode* static struct inode*
createat_x(int dirfd, char *path, short type, short major, short minor, createat_x(int dirfd, char *path, short type, short major, short minor,
int *rofs) int *err)
{ {
struct inode *ip, *dp; struct inode *ip, *dp;
char name[DIRSIZ]; char name[DIRSIZ];
uint off; uint off;
if (!(dp = at_nameiparent(dirfd, path, name))) return 0; if (!(dp = at_nameiparent(dirfd, path, name))) return 0;
ilock(dp); ilock(dp);
// Nothing below this point tolerates a refusal: ialloc and dirlink both // `err` lets the caller report why. Returning 0 alone made a refused
// panic on failure, because on a writable filesystem they cannot fail for
// any reason the caller could have avoided.
//
// The flag lets the caller report EROFS. Returning 0 alone made a refused
// write say "No such file or directory", which is what a shell prints for // write say "No such file or directory", which is what a shell prints for
// every create failure and tells the user nothing about why. // every create failure and tells the user nothing about why.
if (fs_readonly(dp->dev)) { if (fs_readonly(dp->dev)) {
iunlockput(dp); iunlockput(dp);
if (rofs) *rofs = 1; if (err) *err = EROFS;
return 0;
}
// A FAT volume has nowhere to put a device node, a symlink or a socket.
if (dp->ops->refuse_types & (1u << type)) {
iunlockput(dp);
if (err) *err = EPERM;
return 0; return 0;
} }
if ((ip = dirlookup(dp, name, &off)) != 0) { if ((ip = dirlookup(dp, name, &off)) != 0) {
@ -208,7 +211,15 @@ createat_x(int dirfd, char *path, short type, short major, short minor,
iunlockput(ip); iunlockput(ip);
return 0; return 0;
} }
if (!(ip = ialloc(dp->dev, type))) panic("create: ialloc"); // v1 and SchFS cannot fail either of these for a reason the caller could
// have avoided. FAT can: the volume fills up, the fixed root directory of
// FAT16 holds 512 names and no more, and a name may hold characters FAT
// does not allow. So a failure is undone and reported, not a panic.
if (!(ip = ialloc(dp->dev, type))) {
iunlockput(dp);
if (err) *err = ENOSPC;
return 0;
}
ilock(ip); ilock(ip);
ip->major = major; ip->minor = minor; ip->nlink = 1; ip->major = major; ip->minor = minor; ip->nlink = 1;
ip->showmode = 1; ip->showmode = 1;
@ -218,17 +229,20 @@ createat_x(int dirfd, char *path, short type, short major, short minor,
iupdate(dp); iupdate(dp);
build_dir_tree(ip, dp->inum); build_dir_tree(ip, dp->inum);
} }
if (dirlink(dp, name, ip->inum) < 0) panic("create: dirlink"); int r;
if ((r = dirlink(dp, name, ip->inum)) < 0) {
if (type == T_DIR) { dp->nlink--; iupdate(dp); }
iunlockput(dp);
ip->nlink = 0;
iupdate(ip);
iunlockput(ip); // the last reference: ifree gives it back
if (err) *err = r == -EINVAL ? EINVAL : ENOSPC;
return 0;
}
iunlockput(dp); iunlockput(dp);
return ip; return ip;
} }
static struct inode*
createat(int dirfd, char *path, short type, short major, short minor)
{
return createat_x(dirfd, path, type, major, minor, 0);
}
static struct inode* static struct inode*
create(char *path, short type, short major, short minor) create(char *path, short type, short major, short minor)
{ {
@ -508,14 +522,14 @@ addr_t sys_open(void)
struct inode *ex = namei(path); struct inode *ex = namei(path);
if (ex) { iput(ex); end_op(); return -EEXIST; } if (ex) { iput(ex); end_op(); return -EEXIST; }
} }
int rofs = 0; int cerr = 0;
ip = createat_x(AT_FDCWD, path, ip = createat_x(AT_FDCWD, path,
(omode & O_EXTENT) ? T_EXTENT : T_FILE, 0, 0, &rofs); (omode & O_EXTENT) ? T_EXTENT : T_FILE, 0, 0, &cerr);
// A refusal because the filesystem is read-only is not "no such file". // A refusal because the filesystem is read-only is not "no such file".
// A shell prints the same sentence for every create failure, so // A shell prints the same sentence for every create failure, so
// returning ENOENT here made writing to a snapshot look like a missing // returning ENOENT here made writing to a snapshot look like a missing
// directory. // directory.
if (!ip) { end_op(); return rofs ? -EROFS : -ENOENT; } if (!ip) { end_op(); return cerr ? -cerr : -ENOENT; }
if ((omode & O_TRUNC) && (ip->type == T_FILE || ip->type == T_EXTENT)) if ((omode & O_TRUNC) && (ip->type == T_FILE || ip->type == T_EXTENT))
itrunc(ip); itrunc(ip);
} else { } else {
@ -562,7 +576,11 @@ addr_t sys_openat(void)
struct inode *ex = at_namei(dirfd, path); struct inode *ex = at_namei(dirfd, path);
if (ex) { iput(ex); end_op(); return -EEXIST; } if (ex) { iput(ex); end_op(); return -EEXIST; }
} }
if (!(ip = createat(dirfd, path, T_FILE, 0, 0))) { end_op(); return -ENOENT; } int cerr = 0;
if (!(ip = createat_x(dirfd, path, T_FILE, 0, 0, &cerr))) {
end_op();
return cerr ? -cerr : -ENOENT;
}
} else { } else {
if (!(ip = at_namei(dirfd, path))) { end_op(); return -ENOENT; } if (!(ip = at_namei(dirfd, path))) { end_op(); return -ENOENT; }
ilock(ip); ilock(ip);
@ -748,7 +766,11 @@ addr_t sys_mkdir(void)
end_op(); end_op();
return -EEXIST; return -EEXIST;
} }
if (!(ip = create(path, T_DIR, 0, 0))) { end_op(); return -ENOENT; } int cerr = 0;
if (!(ip = createat_x(AT_FDCWD, path, T_DIR, 0, 0, &cerr))) {
end_op();
return cerr ? -cerr : -ENOENT;
}
iunlockput(ip); end_op(); iunlockput(ip); end_op();
return 0; return 0;
} }
@ -799,6 +821,7 @@ addr_t sys_link(void)
if (!(dp = nameiparent(new, name))) { err = ENOENT; goto bad_link; } if (!(dp = nameiparent(new, name))) { err = ENOENT; goto bad_link; }
ilock(dp); ilock(dp);
if (dp->dev != ip->dev) { iunlockput(dp); err = EXDEV; goto bad_link; } if (dp->dev != ip->dev) { iunlockput(dp); err = EXDEV; goto bad_link; }
if (dp->ops->no_hardlinks) { iunlockput(dp); err = EPERM; goto bad_link; }
if (dirlink(dp, name, ip->inum) < 0) { iunlockput(dp); err = EEXIST; goto bad_link; } if (dirlink(dp, name, ip->inum) < 0) { iunlockput(dp); err = EEXIST; goto bad_link; }
iunlockput(dp); iput(ip); end_op(); iunlockput(dp); iput(ip); end_op();
return 0; return 0;
@ -1065,9 +1088,10 @@ do_symlink(int newdirfd, char *target, char *linkpath)
end_op(); end_op();
return -EEXIST; return -EEXIST;
} }
if (!(ip = createat(newdirfd, linkpath, T_SYMLINK, 0, 0))) { int cerr = 0;
if (!(ip = createat_x(newdirfd, linkpath, T_SYMLINK, 0, 0, &cerr))) {
end_op(); end_op();
return -ENOENT; return cerr ? -cerr : -ENOENT;
} }
len = strlen(target); len = strlen(target);
if (writei(ip, 0, (uint64)target, 0, len) != len) { if (writei(ip, 0, (uint64)target, 0, len) != len) {
@ -1538,6 +1562,7 @@ addr_t sys_rmdir(void)
addr_t sys_sync(void) addr_t sys_sync(void)
{ {
schfs_ksync_all(); schfs_ksync_all();
fat_ksync_all(0);
return 0; return 0;
} }
@ -1550,6 +1575,10 @@ addr_t sys_fsync(void)
// this file's last write is in whichever group is open. // this file's last write is in whichever group is open.
if (f->type == FD_INODE && f->ip && SCHFS_IS_VOL_DEV(f->ip->dev)) if (f->type == FD_INODE && f->ip && SCHFS_IS_VOL_DEV(f->ip->dev))
return schfs_ksync(f->ip->dev) == 0 ? 0 : -EIO; return schfs_ksync(f->ip->dev) == 0 ? 0 : -EIO;
// FAT has written everything by the time a write returns; what is left is
// the drive's own cache.
if (f->type == FD_INODE && f->ip && FAT_IS_DEV(f->ip->dev))
fat_ksync_all(0);
return 0; return 0;
} }
@ -1593,7 +1622,59 @@ addr_t sys_pwrite(void)
// utime / utimes / utimensat — timestamps not tracked; stubs. // utime / utimes / utimensat — timestamps not tracked; stubs.
addr_t sys_utime(void) { return 0; } addr_t sys_utime(void) { return 0; }
addr_t sys_utimes(void) { return 0; } addr_t sys_utimes(void) { return 0; }
addr_t sys_utimensat(void) { return 0; } // utimensat / futimens.
//
// This was a stub returning 0, which was worse than not having it: toybox's
// touch calls utimensat first and creates the file only when that fails with
// ENOENT, so `touch newfile` succeeded and made nothing, on every filesystem.
#define UTIME_NOW ((1L << 30) - 1)
#define UTIME_OMIT ((1L << 30) - 2)
addr_t sys_utimensat(void)
{
int dirfd = 0, flags = 0;
addr_t upath = 0, utimes = 0;
struct inode *ip;
struct timespec ts[2];
argint(0, &dirfd);
argaddr(1, &upath);
argaddr(2, &utimes);
argint(3, &flags);
if (utimes && copyin(myproc()->pgdir, utimes, ts, sizeof(ts)) < 0)
return -EFAULT;
if (upath == 0) { // futimens(fd, times)
struct file *f;
if (argfd(0, 0, &f) < 0 || f->type != FD_INODE || !f->ip)
return -EBADF;
ip = idup(f->ip);
} else {
char *path;
if (argstr(1, &path) < 0) return -EFAULT;
begin_op();
ip = at_namei(dirfd, path);
end_op();
if (!ip) return -ENOENT;
}
uint64 now = 0, us = 0;
wallclock(&now, &us);
begin_op();
ilock(ip);
if (fs_readonly(ip->dev)) { iunlockput(ip); end_op(); return -EROFS; }
for (int i = 0; i < 2; i++) {
long sec = utimes ? ts[i].tv_sec : (long)now;
if (utimes && ts[i].tv_nsec == UTIME_OMIT) continue;
if (utimes && ts[i].tv_nsec == UTIME_NOW) sec = (long)now;
if (i == 0) ip->atime = (uint)sec;
else ip->mtime = (uint)sec;
}
ip->ctime = (uint)now;
iupdate(ip);
iunlockput(ip);
end_op();
return 0;
}
// sendfile: not implemented. // sendfile: not implemented.
addr_t sys_sendfile(void) { return -ENOSYS; } addr_t sys_sendfile(void) { return -ENOSYS; }
@ -1637,6 +1718,18 @@ static void fill_statfs(struct statfs *sf, uint dev) {
return; return;
} }
uint64 csize, clusters, freec;
if (fat_kstatfs(dev, &csize, &clusters, &freec) == 0) {
sf->f_type = 0x4d44; // MSDOS_SUPER_MAGIC, as Linux says
sf->f_bsize = (int64)csize;
sf->f_frsize = (int64)csize;
sf->f_blocks = (int64)clusters;
sf->f_bfree = (int64)freec;
sf->f_bavail = (int64)freec;
sf->f_namelen = 255;
return;
}
// v1, /proc, or anything else: the old shape, but at least with a block // v1, /proc, or anything else: the old shape, but at least with a block
// size that matches the one the buffer cache actually uses. // size that matches the one the buffer cache actually uses.
sf->f_bsize = BSIZE; sf->f_bsize = BSIZE;
@ -1697,7 +1790,11 @@ addr_t sys_mkdirat(void)
end_op(); end_op();
return -EEXIST; return -EEXIST;
} }
if (!(ip = createat(dirfd, path, T_DIR, 0, 0))) { end_op(); return -ENOENT; } int cerr = 0;
if (!(ip = createat_x(dirfd, path, T_DIR, 0, 0, &cerr))) {
end_op();
return cerr ? -cerr : -ENOENT;
}
iunlockput(ip); end_op(); iunlockput(ip); end_op();
return 0; return 0;
} }
@ -1714,8 +1811,10 @@ addr_t sys_mknodat(void)
int major = (dev >> 8) & 0xff; int major = (dev >> 8) & 0xff;
int minor = dev & 0xff; int minor = dev & 0xff;
begin_op(); begin_op();
struct inode *ip = createat(dirfd, path, T_DEV, (short)major, (short)minor); int cerr = 0;
if (!ip) { end_op(); return -1; } struct inode *ip = createat_x(dirfd, path, T_DEV, (short)major,
(short)minor, &cerr);
if (!ip) { end_op(); return cerr ? -cerr : -1; }
iunlockput(ip); end_op(); iunlockput(ip); end_op();
return 0; return 0;
} }
@ -1820,7 +1919,8 @@ addr_t sys_linkat(void)
ip->nlink++; iupdate(ip); iunlock(ip); ip->nlink++; iupdate(ip); iunlock(ip);
if (!(dp = at_nameiparent(newdirfd, new, name))) goto bad_linkat; if (!(dp = at_nameiparent(newdirfd, new, name))) goto bad_linkat;
ilock(dp); ilock(dp);
if (dp->dev != ip->dev || dirlink(dp, name, ip->inum) < 0) { if (dp->dev != ip->dev || dp->ops->no_hardlinks ||
dirlink(dp, name, ip->inum) < 0) {
iunlockput(dp); goto bad_linkat; iunlockput(dp); goto bad_linkat;
} }
iunlockput(dp); iput(ip); end_op(); iunlockput(dp); iput(ip); end_op();
@ -1913,6 +2013,55 @@ addr_t sys_fstatat(void)
// decides where in the namespace the result appears. // decides where in the namespace the result appears.
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// The block device a path names: a node with major 8, whose minor is the
// device number. Returns it, or a negative errno.
static int
source_bdev(char *source)
{
begin_op();
struct inode *sp = namei(source);
if (!sp) { end_op(); return -ENOENT; }
ilock(sp);
int r = (sp->type == T_DEV && sp->major == 8) ? sp->minor : -ENOTBLK;
iunlockput(sp);
end_op();
return r;
}
// `mount -t vfat /dev/usb0p1 /mnt`. MS_RDONLY (flag 1, argument 3) mounts it
// read-only; nothing else in the flags or the data means anything to FAT yet.
static int
mount_fat(char *source, char *target)
{
int flags = 0;
argint(3, &flags);
if (!source || !source[0])
return -EINVAL;
int bdev = source_bdev(source);
if (bdev < 0)
return bdev;
begin_op();
struct inode *dp = namei(target);
if (!dp) { end_op(); return -ENOENT; }
ilock(dp);
if (dp->type != T_DIR) { iunlockput(dp); end_op(); return -ENOTDIR; }
uint dev = dp->dev;
uint64 inum = dp->inum;
iunlockput(dp);
end_op();
int fsdev = fat_kmount((uint)bdev, flags & 1);
if (fsdev < 0)
return fsdev;
if (mount_add(dev, inum, (uint)fsdev, FAT_ROOT_INO, target, source,
"vfat") != 0) {
fat_kumount((uint)fsdev);
return -EBUSY;
}
return 0;
}
addr_t sys_mount(void) addr_t sys_mount(void)
{ {
char *source, *target, *fstype; char *source, *target, *fstype;
@ -1934,8 +2083,15 @@ addr_t sys_mount(void)
return 0; return 0;
} }
// Only SchFS. Checking the name still matters: a caller that asked for // FAT, under the three names Linux knows it by. vfat is what everything
// something else should be told no rather than quietly handed this. // means today; msdos is the same driver without long names on Linux, and
// here simply the same driver.
if (strncmp(fstype, "vfat", 5) == 0 || strncmp(fstype, "msdos", 6) == 0 ||
strncmp(fstype, "fat", 4) == 0)
return mount_fat(source, target);
// Otherwise SchFS. Checking the name still matters: a caller that asked
// for something else should be told no rather than quietly handed this.
if (strncmp(fstype, "schfs", 6) != 0) if (strncmp(fstype, "schfs", 6) != 0)
return -ENODEV; return -ENODEV;
@ -2065,6 +2221,13 @@ addr_t sys_umount2(void)
iput(dp); iput(dp);
end_op(); end_op();
// Something still open or a working directory inside it -- the root
// included, which nothing holds once the namei above has let go: the
// volume cannot be let go of, since its inodes would outlive it. Linux
// says EBUSY here, and `umount` prints "target is busy".
if (FAT_IS_DEV(dev) && ibusy(dev, 0))
return -EBUSY;
if (mount_del_by_root(dev, inum) != 0) if (mount_del_by_root(dev, inum) != 0)
return -EINVAL; return -EINVAL;
@ -2079,6 +2242,8 @@ addr_t sys_umount2(void)
// to fall back to -- and mount_del_by_root has already failed for it, // to fall back to -- and mount_del_by_root has already failed for it,
// since nothing mounts the root over anything. // since nothing mounts the root over anything.
schfs_kumount(dev); schfs_kumount(dev);
else if (FAT_IS_DEV(dev))
fat_kumount(dev);
return 0; return 0;
} }

View file

@ -10,6 +10,7 @@
#include "uaccess.h" #include "uaccess.h"
#include "errno.h" #include "errno.h"
#include "acpi.h" #include "acpi.h"
#include "fat.h"
addr_t sys_fork(void) { return fork(); } addr_t sys_fork(void) { return fork(); }
addr_t sys_wait(void) { return wait(); } addr_t sys_wait(void) { return wait(); }
@ -153,6 +154,9 @@ sys_uptime(void)
addr_t sys_reboot(void) addr_t sys_reboot(void)
{ {
cprintf("System is rebooting...\n"); cprintf("System is rebooting...\n");
// A stick left mounted should come back readable, and marked clean.
schfs_ksync_all();
fat_ksync_all(1);
// The FADT's reset register first: it is the one the board's designer // The FADT's reset register first: it is the one the board's designer
// wired up. Then the PCI reset port and the keyboard controller, which // wired up. Then the PCI reset port and the keyboard controller, which
// are what nearly every PC has had since there were PCs. // are what nearly every PC has had since there were PCs.
@ -170,6 +174,7 @@ addr_t sys_shutdown(void)
// kept for a machine whose AML did not load; a real board answers none // kept for a machine whose AML did not load; a real board answers none
// of them. // of them.
schfs_ksync_all(); schfs_ksync_all();
fat_ksync_all(1);
acpi_poweroff(); acpi_poweroff();
outw(0x604, 0x2000); outw(0x604, 0x2000);
outw(0xB004, 0x2000); outw(0xB004, 0x2000);