SchOS/kernel/fs/fat/fat.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

1996 lines
52 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// FAT12, FAT16 and FAT32, with VFAT long file names. See include/fs/fat.h.
//
// Written from Microsoft's FAT specification (the "FAT32 File System
// Specification", 1.03) and matching Linux's vfat where the specification
// leaves a choice: which names get a long-name entry, how the short alias is
// made, the lowercase bits in byte 12, the "not cleanly unmounted" bit. A
// stick written here should look, to Linux, Windows and macOS, like one any of
// them wrote.
//
// ---------------------------------------------------------------------------
// Inodes, on a filesystem that has none
//
// A FAT file is its directory entry: the name, the first cluster, the size and
// the times are all in those 32 bytes, and there is nothing else to point at.
// Linux numbers a FAT inode by the position of that entry on the volume, and
// so does this, with two refinements the VFS here needs:
//
// files TAG_FILE | (byte position of the short entry / 32)
// directories TAG_DIR | first cluster
// the root FAT_ROOT_INO
//
// A directory is numbered by its cluster because ".." has to find it: a
// directory's ".." entry holds its parent's first cluster and nothing about
// where the parent's own entry is.
//
// A file's entry moves when it is renamed, and the VFS renames by linking the
// new name to the same inode number and then unlinking the old one. So a file
// whose entry is not where its number says is recorded in a per-volume table,
// and so is a file created here, which has a number (TAG_NEW) before it has an
// entry. The table is consulted first, in both directions; a number not in it
// means what it says.
//
// ---------------------------------------------------------------------------
// I/O
//
// Through a cache of its own, not the buffer cache. FAT addresses sectors, a
// partition is not obliged to be a whole number of 4 KB blocks, and the buffer
// cache's last block on such a partition would be a write into the next one.
// Every operation that changes anything writes its dirty lines back before it
// returns, so a stick pulled out between two commands holds both of them.
#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "uapi/stat.h"
#include "core/spinlock.h"
#include "core/sleeplock.h"
#include "core/proc.h"
#include "fs/fs.h"
#include "fs/file.h"
#include "uapi/errno.h"
#include "fs/blkdev.h"
#include "fs/fat.h"
#define LINE 4096
#define NLINE 64 // 256 KB of cache per volume
#define NMAP 1024
#define TAG_FILE (1ULL << 40)
#define TAG_DIR (2ULL << 40)
#define TAG_NEW (3ULL << 40)
#define TAG_MASK (0xFFULL << 40)
#define A_RO 0x01
#define A_HIDDEN 0x02
#define A_SYSTEM 0x04
#define A_VOLUME 0x08
#define A_DIR 0x10
#define A_ARCHIVE 0x20
#define A_LFN 0x0F
#define NT_LOWER_BASE 0x08
#define NT_LOWER_EXT 0x10
#define FREE_UNKNOWN 0xFFFFFFFFu
struct line {
uint64 idx; // which LINE-sized piece of the volume
int valid, dirty;
uint use;
uchar *data;
};
struct imap {
uint64 inum;
uint64 pos; // 0: no entry -- not yet linked, or unlinked
};
#define MAXNAME 255
// One name in a directory: its long-name entries, if any, and the short entry
// they belong to.
struct found {
uint32 first_i, sfn_i; // entry indexes: where the group starts, ends
uint64 sfn_pos;
uchar e[32];
uint16 name[MAXNAME + 1];
int nlen;
};
struct fvol {
int used, ro, err;
uint bdev;
uint32 secsize;
uint64 bytes;
int fat; // 12, 16 or 32
uint32 clsize, nfats;
uint64 fat_off, fat_bytes; // the first FAT this writes, and one's size
uint64 root_off; // FAT12/16: the fixed root directory
uint32 root_ents;
uint32 root_clus; // FAT32
uint64 data_off; // cluster 2
uint32 nclus; // data clusters; valid ones are 2..nclus+1
uint64 fsinfo_off; // 0 when there is none
uint32 free_count, next_free;
int info_dirty;
uint64 state_off; // the boot sector's "dirty" byte, or 0
struct sleeplock lk;
struct line line[NLINE];
uint clock;
struct imap map[NMAP];
int nmap;
uint64 nextnew;
// Scratch for the operation that holds `lk`. Here rather than on the stack,
// which is 8 KB and already deep by the time a path lookup gets this far:
// a name is 255 UTF-16 units, and a lookup wants three of them at once.
struct found scan, hit;
uint16 lfn[20 * 13];
uint16 nm[MAXNAME + 1];
char u8[MAXNAME * 3 + 1];
};
static struct fvol vols[FAT_VOL_SLOTS];
static struct spinlock slotlk;
static int slotinit;
static struct fvol *
vol_of(uint dev)
{
if (!FAT_IS_DEV(dev))
return 0;
struct fvol *v = &vols[dev - FAT_DEV];
return v->used ? v : 0;
}
static uint16 le16(const uchar *p) { return p[0] | p[1] << 8; }
static uint32 le32(const uchar *p)
{ return p[0] | p[1] << 8 | p[2] << 16 | (uint32)p[3] << 24; }
static void put16(uchar *p, uint32 v) { p[0] = v; p[1] = v >> 8; }
static void put32(uchar *p, uint32 v)
{ p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24; }
// --- the cache ---------------------------------------------------------------
static int
line_io(struct fvol *v, struct line *l, int write)
{
uint64 off = l->idx * LINE;
uint64 n = v->bytes - off;
if (n > LINE)
n = LINE;
return blkdev_xfer(v->bdev, off / v->secsize, (uint32)(n / v->secsize),
l->data, write);
}
static int
line_out(struct fvol *v, struct line *l)
{
if (!l->dirty)
return 0;
l->dirty = 0;
if (line_io(v, l, 1) < 0) {
if (!v->err)
cprintf("fat: %s: write failed; the volume may be damaged\n",
blkdev_name(v->bdev));
v->err = 1;
return -1;
}
return 0;
}
// The line holding byte LINE*idx. `whole` when the caller is about to
// overwrite all of it, which saves reading what is about to be replaced.
static struct line *
line_get(struct fvol *v, uint64 idx, int whole)
{
struct line *l, *victim = 0;
for (l = v->line; l < v->line + NLINE; l++)
if (l->valid && l->idx == idx) {
l->use = ++v->clock;
return l;
}
for (l = v->line; l < v->line + NLINE; l++) {
if (!l->valid) { victim = l; break; }
if (!victim || l->use < victim->use)
victim = l;
}
l = victim;
if (l->valid)
line_out(v, l);
if (!l->data && (l->data = (uchar *)kalloc()) == 0)
return 0;
l->valid = 0;
l->idx = idx;
if (!whole && line_io(v, l, 0) < 0) {
if (!v->err)
cprintf("fat: %s: read failed\n", blkdev_name(v->bdev));
v->err = 1;
return 0;
}
l->valid = 1;
l->dirty = 0;
l->use = ++v->clock;
return l;
}
// Move n bytes between the volume at `off` and `addr`, which is a kernel
// address or, with `user`, one in the current process.
static int
vio_x(struct fvol *v, uint64 off, uint64 addr, uint64 n, int write, int user)
{
if (off + n > v->bytes || off + n < off)
return -1;
while (n) {
uint64 idx = off / LINE, o = off % LINE, k = LINE - o;
if (k > n)
k = n;
struct line *l = line_get(v, idx, write && o == 0 && k == LINE);
if (!l)
return -1;
if (write) {
if (user) {
if (copyin(myproc()->pgdir, (addr_t)addr, l->data + o, k) < 0)
return -1;
} else
memmove(l->data + o, (void *)addr, k);
l->dirty = 1;
} else {
if (user) {
if (copyout(myproc()->pgdir, (addr_t)addr, l->data + o, k) < 0)
return -1;
} else
memmove((void *)addr, l->data + o, k);
}
addr += k;
off += k;
n -= k;
}
return 0;
}
static int
vio(struct fvol *v, uint64 off, void *buf, uint64 n, int write)
{
return vio_x(v, off, (uint64)buf, n, write, 0);
}
static void
write_fsinfo(struct fvol *v)
{
uchar b[8];
if (!v->fsinfo_off || !v->info_dirty)
return;
put32(b, v->free_count);
put32(b + 4, v->next_free);
vio(v, v->fsinfo_off + 488, b, 8, 1);
v->info_dirty = 0;
}
// Everything dirty, out. `device` also asks the drive to commit its own
// cache, which is a sync's job rather than every write's.
static int
flush(struct fvol *v, int device)
{
int r = 0;
if (v->ro)
return 0;
for (struct line *l = v->line; l < v->line + NLINE; l++)
if (l->valid && line_out(v, l) < 0)
r = -1;
if (device)
blkdev_flush(v->bdev);
return r;
}
// --- the allocation table ----------------------------------------------------
static int
valid_clus(struct fvol *v, uint32 c)
{
return c >= 2 && c < v->nclus + 2;
}
static uint32
eoc(struct fvol *v)
{
return v->fat == 32 ? 0x0FFFFFFF : v->fat == 16 ? 0xFFFF : 0xFFF;
}
static uint32
fat_get(struct fvol *v, uint32 c)
{
uchar b[4] = { 0, 0, 0, 0 };
if (v->fat == 32) {
if (vio(v, v->fat_off + (uint64)c * 4, b, 4, 0) < 0)
return 0x0FFFFFFF;
return le32(b) & 0x0FFFFFFF;
}
if (v->fat == 16) {
if (vio(v, v->fat_off + (uint64)c * 2, b, 2, 0) < 0)
return 0xFFFF;
return le16(b);
}
if (vio(v, v->fat_off + c + c / 2, b, 2, 0) < 0)
return 0xFFF;
return (c & 1) ? le16(b) >> 4 : le16(b) & 0xFFF;
}
// The next cluster in a chain, or 0 at its end -- whether that end is an
// end-of-chain mark, a bad-cluster mark or a value pointing nowhere.
static uint32
next_clus(struct fvol *v, uint32 c)
{
uint32 n = fat_get(v, c);
return valid_clus(v, n) ? n : 0;
}
// Every copy of the table, unless the volume says only one is live.
static void
fat_set(struct fvol *v, uint32 c, uint32 val)
{
uchar b[4];
for (uint32 i = 0; i < v->nfats; i++) {
uint64 base = v->fat_off + i * v->fat_bytes;
if (v->fat == 32) {
uint64 o = base + (uint64)c * 4;
vio(v, o, b, 4, 0);
put32(b, (le32(b) & 0xF0000000) | (val & 0x0FFFFFFF));
vio(v, o, b, 4, 1);
} else if (v->fat == 16) {
put16(b, val);
vio(v, base + (uint64)c * 2, b, 2, 1);
} else {
uint64 o = base + c + c / 2;
vio(v, o, b, 2, 0);
uint32 x = le16(b);
x = (c & 1) ? (x & 0x000F) | ((val & 0xFFF) << 4)
: (x & 0xF000) | (val & 0xFFF);
put16(b, x);
vio(v, o, b, 2, 1);
}
}
}
static uint64
clus_off(struct fvol *v, uint32 c)
{
return v->data_off + (uint64)(c - 2) * v->clsize;
}
static void
zero_clus(struct fvol *v, uint32 c)
{
uint64 off = clus_off(v, c), end = off + v->clsize;
while (off < end) {
uint64 idx = off / LINE, o = off % LINE, k = LINE - o;
if (k > end - off)
k = end - off;
struct line *l = line_get(v, idx, o == 0 && k == LINE);
if (!l)
return;
memset(l->data + o, 0, k);
l->dirty = 1;
off += k;
}
}
// A free cluster, zeroed, marked end-of-chain and linked after `prev` if
// there is one. 0 when the volume is full.
//
// Zeroed because FAT has no holes: a file extended past its end reads back as
// whatever the new cluster held, and a directory's end is the first entry
// whose first byte is zero. The zeroing happens in the cache, so a cluster
// that is about to be written in full costs no extra I/O.
static uint32
clus_alloc(struct fvol *v, uint32 prev)
{
uint32 start = valid_clus(v, v->next_free) ? v->next_free : 2;
if (v->free_count == 0)
return 0;
for (uint32 i = 0; i < v->nclus; i++) {
uint32 c = 2 + (start - 2 + i) % v->nclus;
if (fat_get(v, c) != 0)
continue;
fat_set(v, c, eoc(v));
if (prev)
fat_set(v, prev, c);
zero_clus(v, c);
v->next_free = c + 1;
if (v->free_count != FREE_UNKNOWN)
v->free_count--;
v->info_dirty = 1;
return c;
}
v->free_count = 0;
return 0;
}
static void
free_chain(struct fvol *v, uint32 c)
{
// Bounded by the size of the volume: a chain that loops is damage, and
// following it would never end.
for (uint32 n = 0; valid_clus(v, c) && n <= v->nclus; n++) {
uint32 next = fat_get(v, c);
fat_set(v, c, 0);
if (v->free_count != FREE_UNKNOWN)
v->free_count++;
v->info_dirty = 1;
c = valid_clus(v, next) ? next : 0;
}
}
static uint32
count_free(struct fvol *v)
{
uint32 n = 0;
for (uint32 c = 2; c < v->nclus + 2; c++)
if (fat_get(v, c) == 0)
n++;
return n;
}
// --- directories -------------------------------------------------------------
// A walk over one directory's entries by index. `first` is its first
// cluster, or 0 for the fixed root directory of FAT12 and FAT16. Remembers
// where it is, so stepping forward is not a walk from the start each time.
struct dwalk {
struct fvol *v;
uint32 first, clus, cidx;
};
static void
dwalk_init(struct dwalk *d, struct fvol *v, uint32 first)
{
d->v = v;
d->first = first;
d->clus = 0;
d->cidx = 0;
}
// The volume position of entry i, or 0 past the end of the directory.
static uint64
ent_pos(struct dwalk *d, uint32 i)
{
struct fvol *v = d->v;
if (d->first == 0)
return i < v->root_ents ? v->root_off + (uint64)i * 32 : 0;
uint32 per = v->clsize / 32, want = i / per;
if (d->clus == 0 || want < d->cidx) {
d->clus = d->first;
d->cidx = 0;
}
while (d->cidx < want) {
uint32 n = next_clus(v, d->clus);
if (n == 0)
return 0;
d->clus = n;
d->cidx++;
}
return clus_off(v, d->clus) + (uint64)(i % per) * 32;
}
// The cluster a directory inode's entries start in.
static uint32
dir_first(struct fvol *v, struct inode *dp)
{
if (dp->inum == FAT_ROOT_INO)
return v->fat == 32 ? v->root_clus : 0;
return (uint32)(dp->inum & ~TAG_MASK);
}
static uint32
ent_clus(struct fvol *v, const uchar *e)
{
uint32 c = le16(e + 26);
if (v->fat == 32)
c |= (uint32)le16(e + 20) << 16;
return c;
}
static void
set_clus(uchar *e, uint32 c)
{
put16(e + 26, c & 0xFFFF);
put16(e + 20, c >> 16);
}
// --- names -------------------------------------------------------------------
// Code page 437, 0x80-0xFF, which is what a short name's high bytes mean
// unless a volume says otherwise -- and none say.
static const uint16 cp437[128] = {
0x00C7,0x00FC,0x00E9,0x00E2,0x00E4,0x00E0,0x00E5,0x00E7,
0x00EA,0x00EB,0x00E8,0x00EF,0x00EE,0x00EC,0x00C4,0x00C5,
0x00C9,0x00E6,0x00C6,0x00F4,0x00F6,0x00F2,0x00FB,0x00F9,
0x00FF,0x00D6,0x00DC,0x00A2,0x00A3,0x00A5,0x20A7,0x0192,
0x00E1,0x00ED,0x00F3,0x00FA,0x00F1,0x00D1,0x00AA,0x00BA,
0x00BF,0x2310,0x00AC,0x00BD,0x00BC,0x00A1,0x00AB,0x00BB,
0x2591,0x2592,0x2593,0x2502,0x2524,0x2561,0x2562,0x2556,
0x2555,0x2563,0x2551,0x2557,0x255D,0x255C,0x255B,0x2510,
0x2514,0x2534,0x252C,0x251C,0x2500,0x253C,0x255E,0x255F,
0x255A,0x2554,0x2569,0x2566,0x2560,0x2550,0x256C,0x2567,
0x2568,0x2564,0x2565,0x2559,0x2558,0x2552,0x2553,0x256B,
0x256A,0x2518,0x250C,0x2588,0x2584,0x258C,0x2590,0x2580,
0x03B1,0x00DF,0x0393,0x03C0,0x03A3,0x03C3,0x00B5,0x03C4,
0x03A6,0x0398,0x03A9,0x03B4,0x221E,0x03C6,0x03B5,0x2229,
0x2261,0x00B1,0x2265,0x2264,0x2320,0x2321,0x00F7,0x2248,
0x00B0,0x2219,0x00B7,0x221A,0x207F,0x00B2,0x25A0,0x00A0,
};
// UTF-8 to UTF-16. Returns the length in code units, or -1 for a name that
// is not valid UTF-8 or will not fit.
static int
utf8_to_16(const char *s, int n, uint16 *out)
{
int len = 0;
for (int i = 0; i < n;) {
uint32 c = (uchar)s[i];
int extra = c < 0x80 ? 0 : (c & 0xE0) == 0xC0 ? 1 : (c & 0xF0) == 0xE0 ? 2
: (c & 0xF8) == 0xF0 ? 3 : -1;
if (extra < 0 || i + extra >= n + (extra ? 0 : 1))
return -1;
if (extra)
c &= 0x3F >> extra;
for (int k = 1; k <= extra; k++) {
uchar b = s[i + k];
if ((b & 0xC0) != 0x80)
return -1;
c = (c << 6) | (b & 0x3F);
}
i += 1 + extra;
if (c >= 0x10000) {
if (len + 2 > MAXNAME)
return -1;
c -= 0x10000;
out[len++] = 0xD800 | (c >> 10);
out[len++] = 0xDC00 | (c & 0x3FF);
} else {
if (len + 1 > MAXNAME)
return -1;
out[len++] = c;
}
}
return len;
}
// UTF-16 to UTF-8, NUL-terminated. Returns the byte length.
static int
utf16_to_8(const uint16 *s, int n, char *out, int cap)
{
int len = 0;
for (int i = 0; i < n; i++) {
uint32 c = s[i];
if (c >= 0xD800 && c < 0xDC00 && i + 1 < n &&
s[i + 1] >= 0xDC00 && s[i + 1] < 0xE000) {
c = 0x10000 + ((c - 0xD800) << 10) + (s[i + 1] - 0xDC00);
i++;
}
char t[4];
int k;
if (c < 0x80) { t[0] = c; k = 1; }
else if (c < 0x800) { t[0] = 0xC0 | c >> 6; t[1] = 0x80 | (c & 0x3F); k = 2; }
else if (c < 0x10000){ t[0] = 0xE0 | c >> 12; t[1] = 0x80 | ((c >> 6) & 0x3F);
t[2] = 0x80 | (c & 0x3F); k = 3; }
else { t[0] = 0xF0 | c >> 18; t[1] = 0x80 | ((c >> 12) & 0x3F);
t[2] = 0x80 | ((c >> 6) & 0x3F); t[3] = 0x80 | (c & 0x3F); k = 4; }
if (len + k >= cap)
break;
for (int j = 0; j < k; j++)
out[len++] = t[j];
}
out[len] = 0;
return len;
}
// FAT names compare without regard to case. ASCII and Latin-1 are folded;
// beyond that a name has to match as written, which is what Linux does too
// without a full Unicode case table.
static uint16
fold(uint16 c)
{
if (c >= 'a' && c <= 'z')
return c - 32;
if (c >= 0xE0 && c <= 0xFE && c != 0xF7)
return c - 32;
return c;
}
static int
name_eq(const uint16 *a, int an, const uint16 *b, int bn)
{
if (an != bn)
return 0;
for (int i = 0; i < an; i++)
if (fold(a[i]) != fold(b[i]))
return 0;
return 1;
}
// The short name as it is shown: "README.TXT", or "readme.txt" when byte 12
// says the parts were lowercase -- which is how Windows NT and Linux store an
// all-lowercase 8.3 name without a long-name entry.
static int
sfn_name(const uchar *e, uint16 *out)
{
int n = 0, b = 8, x = 3;
while (b > 0 && e[b - 1] == ' ')
b--;
while (x > 0 && e[8 + x - 1] == ' ')
x--;
for (int i = 0; i < b; i++) {
uchar c = (i == 0 && e[0] == 0x05) ? 0xE5 : e[i];
if ((e[12] & NT_LOWER_BASE) && c >= 'A' && c <= 'Z')
c += 32;
out[n++] = c < 0x80 ? c : cp437[c - 0x80];
}
if (x) {
out[n++] = '.';
for (int i = 0; i < x; i++) {
uchar c = e[8 + i];
if ((e[12] & NT_LOWER_EXT) && c >= 'A' && c <= 'Z')
c += 32;
out[n++] = c < 0x80 ? c : cp437[c - 0x80];
}
}
return n;
}
static uchar
sfn_sum(const uchar *e)
{
uchar s = 0;
for (int i = 0; i < 11; i++)
s = ((s & 1) << 7) + (s >> 1) + e[i];
return s;
}
// A name the VFS hands over, as UTF-16, with the trailing dots Windows and
// Linux both drop. -1 for one FAT cannot hold.
static int
vfs_name(const char *name, uint16 *out)
{
int n = 0;
while (n < VFS_NAME_MAX && name[n])
n++;
int len = utf8_to_16(name, n, out);
if (len < 0)
return -1;
if (!(len == 1 && out[0] == '.') && !(len == 2 && out[0] == '.' && out[1] == '.'))
while (len > 0 && out[len - 1] == '.')
len--;
return len > 0 ? len : -1;
}
static int
lfn_char_ok(uint16 c)
{
if (c < 0x20)
return 0;
switch (c) {
case '"': case '*': case '/': case ':': case '<': case '>': case '?':
case '\\': case '|':
return 0;
}
return 1;
}
static int
sfn_char_ok(uint16 c)
{
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
return 1;
switch (c) {
case '!': case '#': case '$': case '%': case '&': case '\'': case '(':
case ')': case '-': case '@': case '^': case '_': case '`': case '{':
case '}': case '~':
return 1;
}
return 0;
}
// Does the name fit 8.3 as it stands? If it does, fill in the eleven bytes
// and byte 12's lowercase bits, and no long-name entry is needed. Mixed case
// within a part ("Readme") does not fit: only a long name can keep it.
static int
fits_83(const uint16 *s, int n, uchar *sfn, uchar *nt)
{
int dot = -1, lower[2] = { 0, 0 }, upper[2] = { 0, 0 };
for (int i = 0; i < n; i++)
if (s[i] == '.') {
if (dot >= 0)
return 0;
dot = i;
}
int blen = dot < 0 ? n : dot, xlen = dot < 0 ? 0 : n - dot - 1;
if (blen < 1 || blen > 8 || xlen > 3 || (dot >= 0 && xlen == 0))
return 0;
memset(sfn, ' ', 11);
for (int i = 0; i < n; i++) {
if (i == dot)
continue;
int part = dot >= 0 && i > dot;
uint16 c = s[i];
if (c >= 'a' && c <= 'z') { lower[part] = 1; c -= 32; }
else if (c >= 'A' && c <= 'Z') upper[part] = 1;
if (!sfn_char_ok(c))
return 0;
sfn[part ? 8 + (i - dot - 1) : i] = c;
}
if ((lower[0] && upper[0]) || (lower[1] && upper[1]))
return 0;
if (sfn[0] == 0xE5)
sfn[0] = 0x05;
*nt = (lower[0] ? NT_LOWER_BASE : 0) | (lower[1] ? NT_LOWER_EXT : 0);
return 1;
}
// --- scanning a directory ----------------------------------------------------
typedef int (*scan_fn)(struct fvol *, struct found *, void *);
// Call fn for every name from entry `from` on, stopping when it returns
// non-zero (and returning that). `*end` is where the directory ends.
static int
dir_scan(struct fvol *v, uint32 first, uint32 from, scan_fn fn, void *arg,
uint32 *end)
{
struct dwalk d;
struct found *fp = &v->scan;
uint16 *lfn = v->lfn;
int have = 0, expect = 0, total = 0;
uchar sum = 0;
uint32 start = 0, i;
dwalk_init(&d, v, first);
for (i = from;; i++) {
uint64 pos = ent_pos(&d, i);
uchar e[32];
if (pos == 0 || vio(v, pos, e, 32, 0) < 0)
break;
if (e[0] == 0x00)
break; // nothing in use from here on
if (e[0] == 0xE5) {
have = 0;
continue;
}
if ((e[11] & 0x3F) == A_LFN) {
int ord = e[0] & 0x3F;
if (e[0] & 0x40) {
if (ord == 0 || ord > 20) { have = 0; continue; }
have = 1;
total = expect = ord;
sum = e[13];
start = i;
}
if (!have || ord != expect || e[13] != sum) {
have = 0;
continue;
}
uint16 *p = lfn + (ord - 1) * 13;
for (int k = 0; k < 5; k++) p[k] = le16(e + 1 + 2 * k);
for (int k = 0; k < 6; k++) p[5 + k] = le16(e + 14 + 2 * k);
for (int k = 0; k < 2; k++) p[11 + k] = le16(e + 28 + 2 * k);
expect--;
continue;
}
if (e[11] & A_VOLUME) {
have = 0;
continue;
}
memmove(fp->e, e, 32);
fp->sfn_i = i;
fp->sfn_pos = pos;
if (have && expect == 0 && sfn_sum(e) == sum) {
int n = 0;
while (n < total * 13 && lfn[n] != 0x0000 && n < MAXNAME)
n++;
memmove(fp->name, lfn, n * sizeof(uint16));
fp->nlen = n;
fp->first_i = start;
} else {
fp->nlen = sfn_name(e, fp->name);
fp->first_i = i;
}
have = 0;
int r = fn(v, fp, arg);
if (r)
return r;
}
if (end)
*end = i;
return 0;
}
struct want {
const uint16 *name;
int nlen;
struct found *out;
};
static int
match_name(struct fvol *v, struct found *f, void *arg)
{
struct want *w = arg;
uint16 alt[16];
(void)v;
if (name_eq(f->name, f->nlen, w->name, w->nlen)) {
*w->out = *f;
return 1;
}
// A name with a long entry still answers to its short alias, as it does
// everywhere else -- "PROGRA~1" is how a good many scripts reach things.
int an = sfn_name(f->e, alt);
if (name_eq(alt, an, w->name, w->nlen)) {
*w->out = *f;
return 1;
}
return 0;
}
static int
find(struct fvol *v, uint32 first, const uint16 *name, int nlen,
struct found *out)
{
struct want w = { name, nlen, out };
return dir_scan(v, first, 0, match_name, &w, 0) == 1;
}
struct sfnq { const uchar *sfn; };
static int
match_sfn(struct fvol *v, struct found *f, void *arg)
{
(void)v;
return memcmp(f->e, ((struct sfnq *)arg)->sfn, 11) == 0;
}
static int
sfn_taken(struct fvol *v, uint32 first, const uchar *sfn)
{
struct sfnq q = { sfn };
return dir_scan(v, first, 0, match_sfn, &q, 0) == 1;
}
// The short alias for a name that needs a long one, the way Linux makes it.
// A name that only needed one for its case ("Reports", "ReadMe2.Txt") gets
// its uppercase self, "REPORTS", if no other entry has it. Otherwise: the name
// uppercased, without spaces or dots, with anything a short name cannot hold
// turned into '_', cut to six characters and "~1" added -- or "~2", and on,
// until it is unique in the directory.
static int
make_alias(struct fvol *v, uint32 first, const uint16 *s, int n, uchar *sfn)
{
uchar base[8], ext[3];
int blen = 0, xlen = 0, dot = -1, lossy = 0;
for (int i = n - 1; i > 0; i--)
if (s[i] == '.') { dot = i; break; }
if (dot < 0 && s[0] == '.')
lossy = 1; // ".profile": a leading dot is not an extension
for (int i = 0; i < (dot < 0 ? n : dot); i++) {
uint16 c = s[i];
if (c == ' ' || c == '.') { lossy = 1; continue; }
if (blen == 8) { lossy = 1; break; }
if (c >= 'a' && c <= 'z') c -= 32;
if (!sfn_char_ok(c)) { lossy = 1; c = '_'; }
base[blen++] = c;
}
if (dot >= 0)
for (int i = dot + 1; i < n; i++) {
uint16 c = s[i];
if (c == ' ' || c == '.') { lossy = 1; continue; }
if (xlen == 3) { lossy = 1; break; }
if (c >= 'a' && c <= 'z') c -= 32;
if (!sfn_char_ok(c)) { lossy = 1; c = '_'; }
ext[xlen++] = c;
}
if (blen == 0) {
base[blen++] = '_';
lossy = 1;
}
if (!lossy) {
memset(sfn, ' ', 11);
memmove(sfn, base, blen);
memmove(sfn + 8, ext, xlen);
if (sfn[0] == 0xE5)
sfn[0] = 0x05;
if (!sfn_taken(v, first, sfn))
return 0;
}
for (uint32 num = 1; num < 1000000; num++) {
char digits[8];
int nd = 0;
for (uint32 t = num; t; t /= 10)
digits[nd++] = '0' + t % 10;
int keep = 8 - 1 - nd;
if (keep > blen)
keep = blen;
memset(sfn, ' ', 11);
memmove(sfn, base, keep);
sfn[keep] = '~';
for (int k = 0; k < nd; k++)
sfn[keep + 1 + k] = digits[nd - 1 - k];
memmove(sfn + 8, ext, xlen);
if (!sfn_taken(v, first, sfn))
return 0;
}
return -1;
}
// Add a cluster to the end of a directory. The fixed root of FAT12/16 cannot
// grow, and a full one is full.
static int
dir_grow(struct fvol *v, uint32 first)
{
if (first == 0)
return -1;
uint32 c = first, n;
for (uint32 k = 0; (n = next_clus(v, c)) != 0 && k <= v->nclus; k++)
c = n;
return clus_alloc(v, c) ? 0 : -1;
}
// Write a name into a directory: its long-name entries, then `e`, the short
// entry, whose first eleven bytes and byte 12 this fills in. Returns the
// short entry's volume position, or 0.
static uint64
dir_add(struct fvol *v, uint32 first, const uint16 *name, int nlen, uchar *e)
{
uchar nt = 0;
int need_lfn = !fits_83(name, nlen, e, &nt);
if (need_lfn) {
if (make_alias(v, first, name, nlen, e) < 0)
return 0;
nt = 0;
}
e[12] = nt;
int nl = need_lfn ? (nlen + 12) / 13 : 0, count = nl + 1;
// The first run of `count` free entries, growing the directory as needed.
struct dwalk d;
uint32 run = 0, i;
dwalk_init(&d, v, first);
for (i = 0;; i++) {
uint64 pos = ent_pos(&d, i);
if (pos == 0) {
if (dir_grow(v, first) < 0)
return 0;
pos = ent_pos(&d, i);
if (pos == 0)
return 0;
}
uchar b;
if (vio(v, pos, &b, 1, 0) < 0)
return 0;
if (b == 0x00 || b == 0xE5) {
if (++run == (uint32)count)
break;
} else
run = 0;
}
uint32 at = i + 1 - count;
uchar sum = sfn_sum(e);
for (int k = 0; k < nl; k++) {
int ord = nl - k;
uchar l[32];
memset(l, 0, 32);
l[0] = ord | (k == 0 ? 0x40 : 0);
l[11] = A_LFN;
l[13] = sum;
for (int j = 0; j < 13; j++) {
int ci = (ord - 1) * 13 + j;
uint16 c = ci < nlen ? name[ci] : ci == nlen ? 0x0000 : 0xFFFF;
int o = j < 5 ? 1 + 2 * j : j < 11 ? 14 + 2 * (j - 5) : 28 + 2 * (j - 11);
put16(l + o, c);
}
if (vio(v, ent_pos(&d, at + k), l, 32, 1) < 0)
return 0;
}
uint64 pos = ent_pos(&d, at + nl);
if (vio(v, pos, e, 32, 1) < 0)
return 0;
return pos;
}
static void
dir_del(struct fvol *v, uint32 first, struct found *f)
{
struct dwalk d;
uchar gone = 0xE5;
dwalk_init(&d, v, first);
for (uint32 i = f->first_i; i <= f->sfn_i; i++) {
uint64 pos = ent_pos(&d, i);
if (pos)
vio(v, pos, &gone, 1, 1);
}
}
// --- times -------------------------------------------------------------------
static int64
days_from_civil(int64 y, unsigned m, unsigned d)
{
y -= m <= 2;
int64 era = (y >= 0 ? y : y - 399) / 400;
unsigned yoe = (unsigned)(y - era * 400);
unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + (int64)doe - 719468;
}
static void
civil_from_days(int64 z, int *y, int *m, int *d)
{
z += 719468;
int64 era = (z >= 0 ? z : z - 146096) / 146097;
unsigned doe = (unsigned)(z - era * 146097);
unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
unsigned mp = (5 * doy + 2) / 153;
*d = doy - (153 * mp + 2) / 5 + 1;
*m = mp < 10 ? mp + 3 : mp - 9;
*y = (int)(yoe + era * 400) + (*m <= 2);
}
// Times are stored as UTC. FAT has no time zone; Linux's default for one it
// is not told about is the same.
static void
to_fat(uint64 t, uint16 *date, uint16 *time)
{
int y, m, d;
uint64 s = t % 86400;
civil_from_days((int64)(t / 86400), &y, &m, &d);
if (y < 1980) { y = 1980; m = 1; d = 1; s = 0; }
if (y > 2107) { y = 2107; m = 12; d = 31; s = 86399; }
*date = (uint16)(((y - 1980) << 9) | (m << 5) | d);
*time = (uint16)(((s / 3600) << 11) | (((s / 60) % 60) << 5) | ((s % 60) / 2));
}
static uint
from_fat(uint16 date, uint16 time)
{
unsigned m = (date >> 5) & 15, d = date & 31;
if (date == 0)
return 0;
if (m < 1) m = 1;
if (d < 1) d = 1;
int64 days = days_from_civil(1980 + (date >> 9), m, d);
return (uint)(days * 86400 + (time >> 11) * 3600 + ((time >> 5) & 63) * 60 +
(time & 31) * 2);
}
static uint
now(void)
{
uint64 s = 0, us = 0;
wallclock(&s, &us);
return (uint)s;
}
// --- inode numbers -----------------------------------------------------------
static uint64
pos_of(struct fvol *v, uint64 inum)
{
for (int i = 0; i < v->nmap; i++)
if (v->map[i].inum == inum)
return v->map[i].pos;
if ((inum & TAG_MASK) == TAG_FILE)
return (inum & ~TAG_MASK) * 32;
return 0;
}
static void
map_del(struct fvol *v, uint64 inum)
{
for (int i = 0; i < v->nmap; i++)
if (v->map[i].inum == inum) {
v->map[i] = v->map[--v->nmap];
return;
}
}
static void
map_set(struct fvol *v, uint dev, uint64 inum, uint64 pos)
{
if ((inum & TAG_MASK) == TAG_FILE && pos == (inum & ~TAG_MASK) * 32) {
map_del(v, inum);
return;
}
for (int i = 0; i < v->nmap; i++)
if (v->map[i].inum == inum) {
v->map[i].pos = pos;
return;
}
if (v->nmap == NMAP) {
// Full. Anything nobody holds can go: an inode with no references is
// reloaded from scratch by the next iget, and finds its entry by name.
for (int i = 0; i < v->nmap;)
if (!iheld(dev, v->map[i].inum))
v->map[i] = v->map[--v->nmap];
else
i++;
if (v->nmap == NMAP) {
cprintf("fat: inode table full; a renamed file may be seen twice\n");
v->nmap--;
}
}
v->map[v->nmap].inum = inum;
v->map[v->nmap].pos = pos;
v->nmap++;
}
// The number for the entry at `pos`. Normally its position -- but if that
// number is already someone else's, because a file renamed away from here or
// unlinked while open still answers to it, the entry here gets a fresh one.
static uint64
inum_at(struct fvol *v, uint dev, uint64 pos)
{
uint64 natural = TAG_FILE | (pos / 32);
int taken = 0;
for (int i = 0; i < v->nmap; i++) {
if (v->map[i].pos == pos && (v->map[i].inum & TAG_MASK) != TAG_DIR)
return v->map[i].inum;
if (v->map[i].inum == natural)
taken = 1;
}
if (!taken)
return natural;
uint64 fresh = TAG_NEW | ++v->nextnew;
map_set(v, dev, fresh, pos);
return fresh;
}
// The inode number for the name `f`.
static uint64
inum_of(struct fvol *v, uint dev, struct found *f)
{
if (f->e[11] & A_DIR) {
uint32 c = ent_clus(v, f->e);
if (c == 0 || (v->fat == 32 && c == v->root_clus))
return FAT_ROOT_INO;
return TAG_DIR | c;
}
return inum_at(v, dev, f->sfn_pos);
}
// --- fs_ops ------------------------------------------------------------------
static void
fat_iload(struct inode *ip)
{
struct fvol *v = vol_of(ip->dev);
uchar e[32];
ip->major = ip->minor = 0;
ip->rwmode = RW;
ip->supermode = 0;
ip->showmode = 1;
ip->atime = ip->mtime = ip->ctime = 0;
ip->size = 0;
memset(ip->addrs, 0, sizeof(ip->addrs));
ip->type = T_FILE;
ip->nlink = 0;
if (!v) {
ip->valid = 1;
return;
}
acquiresleep(&v->lk);
uint64 pos = pos_of(v, ip->inum);
int have = pos && vio(v, pos, e, 32, 0) == 0 && e[0] != 0xE5 && e[0] != 0;
if (ip->inum == FAT_ROOT_INO || (ip->inum & TAG_MASK) == TAG_DIR) {
ip->type = T_DIR;
ip->nlink = 2;
ip->addrs[0] = ip->inum == FAT_ROOT_INO ? 0 : (uint)(ip->inum & ~TAG_MASK);
} else if (have && !(e[11] & A_DIR)) {
ip->nlink = 1;
ip->size = le32(e + 28);
ip->addrs[0] = ent_clus(v, e);
ip->addrs[1] = e[11];
}
if (have) {
ip->mtime = from_fat(le16(e + 24), le16(e + 22));
ip->ctime = from_fat(le16(e + 16), le16(e + 14));
ip->atime = from_fat(le16(e + 18), 0);
if (ip->ctime == 0)
ip->ctime = ip->mtime;
if (ip->atime == 0)
ip->atime = ip->mtime;
}
releasesleep(&v->lk);
ip->valid = 1;
}
// The directory entry, brought up to date. Caller holds the volume.
static void
store(struct fvol *v, struct inode *ip)
{
uchar e[32];
uint64 pos;
if (v->ro || ip->type == T_DIR || (pos = pos_of(v, ip->inum)) == 0)
return;
if (vio(v, pos, e, 32, 0) < 0 || e[0] == 0xE5 || e[0] == 0)
return;
uint16 d, t;
set_clus(e, ip->addrs[0]);
put32(e + 28, (uint32)ip->size);
if (ip->mtime) {
to_fat(ip->mtime, &d, &t);
put16(e + 24, d);
put16(e + 22, t);
}
if (ip->atime) {
to_fat(ip->atime, &d, &t);
put16(e + 18, d);
}
vio(v, pos, e, 32, 1);
}
static void
fat_istore(struct inode *ip)
{
struct fvol *v = vol_of(ip->dev);
if (!v || v->ro)
return;
acquiresleep(&v->lk);
store(v, ip);
flush(v, 0);
releasesleep(&v->lk);
}
static void
fat_ifree(struct inode *ip)
{
struct fvol *v = vol_of(ip->dev);
if (!v)
return;
acquiresleep(&v->lk);
if (!v->ro) {
free_chain(v, ip->addrs[0]);
write_fsinfo(v);
flush(v, 0);
}
map_del(v, ip->inum);
releasesleep(&v->lk);
}
// The cluster holding byte idx*clsize of a file, allocating up to it when
// `grow`. The last one found is kept in addrs[2..3], so reading or writing a
// file front to back walks its chain once rather than once per call.
static uint32
file_clus(struct fvol *v, struct inode *ip, uint32 idx, int grow)
{
uint32 c, at;
if (ip->addrs[0] == 0) {
if (!grow || (ip->addrs[0] = clus_alloc(v, 0)) == 0)
return 0;
ip->addrs[2] = 0;
}
if (ip->addrs[2] && ip->addrs[2] - 1 <= idx) {
at = ip->addrs[2] - 1;
c = ip->addrs[3];
} else {
at = 0;
c = ip->addrs[0];
}
while (at < idx) {
uint32 n = next_clus(v, c);
if (n == 0) {
if (!grow || (n = clus_alloc(v, c)) == 0)
return 0;
}
c = n;
at++;
}
ip->addrs[2] = idx + 1;
ip->addrs[3] = c;
return c;
}
static int
fat_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n)
{
struct fvol *v = vol_of(ip->dev);
if (ip->type == T_DIR) {
// build_dir_tree() reads a new directory's first two entries in the v1
// layout before overwriting them. FAT writes its own "." and ".." in
// dirlink, so there is nothing to show it.
if (user_dst)
return -EISDIR;
memset((void *)dst, 0, n);
return n;
}
if (!v)
return -EIO;
acquiresleep(&v->lk);
if (off >= ip->size) {
releasesleep(&v->lk);
return 0;
}
if (off + n > ip->size)
n = (uint)(ip->size - off);
uint done = 0;
while (done < n) {
uint64 o = off + done;
uint32 c = file_clus(v, ip, (uint32)(o / v->clsize), 0);
if (c == 0)
break;
uint64 in = o % v->clsize, k = v->clsize - in;
if (k > n - done)
k = n - done;
if (vio_x(v, clus_off(v, c) + in, dst + done, k, 0, user_dst) < 0)
break;
done += (uint)k;
}
releasesleep(&v->lk);
return done ? (int)done : (v->err ? -EIO : 0);
}
static char zeros[512];
// Write n bytes at off; caller holds the volume. Returns what was written.
static uint
put_data(struct fvol *v, struct inode *ip, int user, uint64 src, uint64 off,
uint n, int zero)
{
uint done = 0;
while (done < n) {
uint64 o = off + done;
uint32 c = file_clus(v, ip, (uint32)(o / v->clsize), 1);
if (c == 0)
break;
uint64 in = o % v->clsize, k = v->clsize - in;
if (k > n - done)
k = n - done;
if (zero && k > sizeof(zeros))
k = sizeof(zeros);
if (vio_x(v, clus_off(v, c) + in, zero ? (uint64)zeros : src + done, k, 1,
zero ? 0 : user) < 0)
break;
done += (uint)k;
}
return done;
}
static int
fat_write(struct inode *ip, int user_src, uint64 src, uint64 off, uint n)
{
struct fvol *v = vol_of(ip->dev);
if (ip->type == T_DIR)
return n; // build_dir_tree(); see fat_read
if (!v)
return -EIO;
if (v->ro)
return -EROFS;
if (off >= 0xFFFFFFFFULL)
return -EFBIG;
if (off + n > 0xFFFFFFFFULL)
n = (uint)(0xFFFFFFFFULL - off);
acquiresleep(&v->lk);
// No holes in FAT: a write past the end fills the gap with zeros first.
if (off > ip->size) {
uint gap = (uint)(off - ip->size);
if (put_data(v, ip, 0, 0, ip->size, gap, 1) < gap) {
store(v, ip);
flush(v, 0);
releasesleep(&v->lk);
return -ENOSPC;
}
ip->size = off;
}
uint done = put_data(v, ip, user_src, src, off, n, 0);
if (off + done > ip->size)
ip->size = off + done;
ip->mtime = now();
store(v, ip);
write_fsinfo(v);
int r = flush(v, 0);
releasesleep(&v->lk);
if (done == 0 && n)
return v->free_count == 0 ? -ENOSPC : -EIO;
return r < 0 && done == 0 ? -EIO : (int)done;
}
static void
fat_truncate(struct inode *ip, uint64 newsize)
{
struct fvol *v = vol_of(ip->dev);
if (!v || v->ro || ip->type == T_DIR)
return;
acquiresleep(&v->lk);
if (newsize < ip->size) {
uint32 keep = (uint32)((newsize + v->clsize - 1) / v->clsize);
if (keep == 0) {
free_chain(v, ip->addrs[0]);
ip->addrs[0] = 0;
} else {
uint32 last = file_clus(v, ip, keep - 1, 0);
if (last) {
uint32 rest = next_clus(v, last);
fat_set(v, last, eoc(v));
free_chain(v, rest);
}
}
ip->addrs[2] = 0;
ip->size = newsize;
} else if (newsize > ip->size && newsize < 0xFFFFFFFFULL) {
uint gap = (uint)(newsize - ip->size);
ip->size += put_data(v, ip, 0, 0, ip->size, gap, 1);
}
ip->mtime = now();
store(v, ip);
write_fsinfo(v);
flush(v, 0);
releasesleep(&v->lk);
}
static struct inode *
fat_dirlookup(struct inode *dp, char *name, uint *poff)
{
struct fvol *v = vol_of(dp->dev);
int n;
if (poff)
*poff = 0;
if (!v)
return 0;
acquiresleep(&v->lk);
uint16 *nm = v->nm;
struct found *fp = &v->hit;
if ((n = vfs_name(name, nm)) < 0) {
releasesleep(&v->lk);
return 0;
}
if (n == 1 && nm[0] == '.') {
releasesleep(&v->lk);
return iget(dp->dev, dp->inum);
}
uint32 first = dir_first(v, dp);
uint64 inum = 0;
if (n == 2 && nm[0] == '.' && nm[1] == '.') {
if (dp->inum == FAT_ROOT_INO) {
inum = FAT_ROOT_INO;
} else {
// Entry 1 of every directory but the root is "..", and holds the
// parent's first cluster -- 0 when the parent is the root.
struct dwalk d;
uchar e[32];
dwalk_init(&d, v, first);
uint64 pos = ent_pos(&d, 1);
if (pos && vio(v, pos, e, 32, 0) == 0 && e[0] == '.' && e[1] == '.') {
uint32 c = ent_clus(v, e);
inum = (c == 0 || (v->fat == 32 && c == v->root_clus))
? FAT_ROOT_INO : TAG_DIR | c;
}
}
} else if (find(v, first, nm, n, fp) &&
!(fp->nlen == 1 && fp->name[0] == '.') &&
!(fp->nlen == 2 && fp->name[0] == '.' && fp->name[1] == '.')) {
inum = inum_of(v, dp->dev, fp);
if ((inum & TAG_MASK) == TAG_DIR)
map_set(v, dp->dev, inum, fp->sfn_pos); // for its times
if (poff)
*poff = fp->sfn_i;
}
releasesleep(&v->lk);
return inum ? iget(dp->dev, inum) : 0;
}
static int
fat_dirlink(struct inode *dp, char *name, uint64 inum)
{
struct fvol *v = vol_of(dp->dev);
uchar e[32];
int n;
if (!v)
return -EIO;
if (v->ro)
return -EROFS;
acquiresleep(&v->lk);
uint16 *nm = v->nm;
struct found *fp = &v->hit;
if ((n = vfs_name(name, nm)) < 0) {
releasesleep(&v->lk);
return -EINVAL;
}
if ((n == 1 && nm[0] == '.') || (n == 2 && nm[0] == '.' && nm[1] == '.')) {
releasesleep(&v->lk);
return 0; // written with the directory; see below
}
for (int i = 0; i < n; i++)
if (!lfn_char_ok(nm[i])) {
releasesleep(&v->lk);
return -EINVAL;
}
uint32 first = dir_first(v, dp);
if (find(v, first, nm, n, fp)) {
releasesleep(&v->lk);
return -EEXIST;
}
uint t = now();
uint16 d, tm;
to_fat(t, &d, &tm);
memset(e, 0, 32);
put16(e + 14, tm); put16(e + 16, d); put16(e + 18, d);
put16(e + 22, tm); put16(e + 24, d);
uint64 old = (inum & TAG_MASK) == TAG_DIR ? 0 : pos_of(v, inum);
if ((inum & TAG_MASK) == TAG_DIR) {
// A new directory: its cluster came from ialloc, zeroed. Its own "." and
// ".." go in first; ".." names the parent's cluster, and 0 for the root
// even on FAT32, which is what the specification asks for.
uint32 c = (uint32)(inum & ~TAG_MASK);
uint32 pc = dp->inum == FAT_ROOT_INO ? 0 : first;
uchar dot[32];
memmove(dot, e, 32);
memset(dot, ' ', 11);
dot[0] = '.';
dot[11] = A_DIR;
set_clus(dot, c);
vio(v, clus_off(v, c), dot, 32, 1);
dot[1] = '.';
set_clus(dot, pc);
vio(v, clus_off(v, c) + 32, dot, 32, 1);
e[11] = A_DIR;
set_clus(e, c);
} else if (old && vio(v, old, e, 32, 0) == 0 && e[0] != 0xE5 && e[0] != 0) {
// A second name for a file that has one: rename's first half. The entry
// is copied whole -- cluster, size, attributes, times -- and the old one
// goes when rename unlinks it.
} else {
e[11] = A_ARCHIVE;
}
uint64 pos = dir_add(v, first, nm, n, e);
if (pos)
map_set(v, dp->dev, inum, pos);
write_fsinfo(v);
flush(v, 0);
releasesleep(&v->lk);
return pos ? 0 : -ENOSPC;
}
static int
fat_dirunlink(struct inode *dp, char *name, uint off)
{
struct fvol *v = vol_of(dp->dev);
int n;
(void)off;
if (!v)
return -EIO;
if (v->ro)
return -EROFS;
acquiresleep(&v->lk);
uint16 *nm = v->nm;
struct found *fp = &v->hit;
if ((n = vfs_name(name, nm)) < 0) {
releasesleep(&v->lk);
return -1;
}
uint32 first = dir_first(v, dp);
if (!find(v, first, nm, n, fp)) {
releasesleep(&v->lk);
return -1;
}
uint64 inum = inum_of(v, dp->dev, fp);
dir_del(v, first, fp);
// The inode loses its entry -- unless its number now lives somewhere
// else, which is the old name of a file being renamed.
if ((inum & TAG_MASK) == TAG_DIR)
map_del(v, inum);
else if (pos_of(v, inum) == fp->sfn_pos)
map_set(v, dp->dev, inum, 0);
flush(v, 0);
releasesleep(&v->lk);
return 0;
}
static int
not_dot(struct fvol *v, struct found *f, void *arg)
{
(void)v; (void)arg;
return !((f->nlen == 1 && f->name[0] == '.') ||
(f->nlen == 2 && f->name[0] == '.' && f->name[1] == '.'));
}
static int
fat_dirempty(struct inode *dp)
{
struct fvol *v = vol_of(dp->dev);
if (!v)
return 1;
acquiresleep(&v->lk);
int any = dir_scan(v, dir_first(v, dp), 0, not_dot, 0, 0);
releasesleep(&v->lk);
return !any;
}
static struct inode *
fat_ialloc(uint dev, short type)
{
struct fvol *v = vol_of(dev);
uint64 inum;
if (!v || v->ro)
return 0;
acquiresleep(&v->lk);
if (type == T_DIR) {
uint32 c = clus_alloc(v, 0);
write_fsinfo(v);
flush(v, 0);
releasesleep(&v->lk);
if (c == 0)
return 0;
inum = TAG_DIR | c;
} else {
inum = TAG_NEW | ++v->nextnew;
map_set(v, dev, inum, 0);
releasesleep(&v->lk);
}
return iget(dev, inum);
}
// --- getdents64 --------------------------------------------------------------
//
// The cookie is 0 for ".", 1 for "..", and 2 + the index of the next entry to
// look at after that -- always the start of a name, never the middle of one.
struct gd {
uint dev;
char *out;
int cap, written;
uint32 next;
};
static int
gd_put(struct gd *g, uint64 ino, int dtype, const char *name, int nlen,
uint64 cookie)
{
int reclen = (19 + nlen + 1 + 7) & ~7;
if (g->written + reclen > g->cap)
return -1;
char *k = g->out + g->written;
*(uint64 *)k = ino;
*(uint64 *)(k + 8) = cookie;
*(uint16 *)(k + 16) = (uint16)reclen;
*(uint8 *)(k + 18) = dtype;
memmove(k + 19, name, nlen);
memset(k + 19 + nlen, 0, reclen - 19 - nlen);
g->written += reclen;
return 0;
}
static int
gd_entry(struct fvol *v, struct found *f, void *arg)
{
struct gd *g = arg;
char *name = v->u8;
if (!not_dot(v, f, 0)) {
g->next = f->sfn_i + 1;
return 0;
}
int n = utf16_to_8(f->name, f->nlen, name, sizeof(v->u8));
int dir = (f->e[11] & A_DIR) != 0;
if (gd_put(g, inum_of(v, g->dev, f), dir ? 4 : 8, name, n, 2 + f->sfn_i + 1) < 0)
return 1;
g->next = f->sfn_i + 1;
return 0;
}
static int
fat_getdents(struct inode *dp, char *out, int cap, uint64 *off)
{
struct fvol *v = vol_of(dp->dev);
struct gd g = { dp->dev, out, cap, 0, 0 };
if (!v)
return -EIO;
if (*off == 0) {
if (gd_put(&g, dp->inum, 4, ".", 1, 1) < 0)
return -EINVAL;
*off = 1;
}
if (*off == 1) {
if (gd_put(&g, dp->inum, 4, "..", 2, 2) < 0)
return g.written ? g.written : -EINVAL;
*off = 2;
}
acquiresleep(&v->lk);
uint32 end = 0;
g.next = (uint32)(*off - 2);
int stopped = dir_scan(v, dir_first(v, dp), g.next, gd_entry, &g, &end);
releasesleep(&v->lk);
if (stopped && g.written == 0)
return -EINVAL; // one name bigger than the whole buffer
*off = 2 + (stopped ? g.next : end);
return g.written;
}
const struct fs_ops fat_ops = {
.name = "vfat",
.iload = fat_iload,
.istore = fat_istore,
.ifree = fat_ifree,
.read = fat_read,
.write = fat_write,
.truncate = fat_truncate,
.dirlookup = fat_dirlookup,
.dirlink = fat_dirlink,
.dirunlink = fat_dirunlink,
.dirempty = fat_dirempty,
.ialloc = fat_ialloc,
.getdents = fat_getdents,
// Files and directories, and one name each. A device node, a symlink or a
// socket has nowhere to go in a FAT entry, and neither has a second name
// for the same clusters -- a hard link here would be what fsck calls a
// cross-linked file.
.refuse_types = (1 << T_DEV) | (1 << T_SYMLINK) | (1 << T_SOCK),
.no_hardlinks = 1,
};
// --- mounting ----------------------------------------------------------------
static void
release_lines(struct fvol *v)
{
for (struct line *l = v->line; l < v->line + NLINE; l++) {
if (l->data)
kfree((char *)l->data);
l->data = 0;
l->valid = l->dirty = 0;
}
}
// The boot sector's "volume dirty" bit, which Linux sets for as long as it
// has a volume mounted read-write and clears when it lets go, and which
// Windows and fsck.fat read as "this was not cleanly unmounted".
static void
mark(struct fvol *v, int dirty)
{
uchar b;
if (!v->state_off || v->ro || vio(v, v->state_off, &b, 1, 0) < 0)
return;
b = dirty ? (b | 1) : (b & ~1);
vio(v, v->state_off, &b, 1, 1);
}
static int
probe(struct fvol *v)
{
uchar bs[512];
if (vio(v, 0, bs, 512, 0) < 0)
return -1;
if (bs[510] != 0x55 || bs[511] != 0xAA || (bs[0] != 0xEB && bs[0] != 0xE9))
return -1;
uint32 bps = le16(bs + 11), spc = bs[13], rsvd = le16(bs + 14);
uint32 nfats = bs[16], rootents = le16(bs + 17);
uint32 tot = le16(bs + 19) ? le16(bs + 19) : le32(bs + 32);
uint32 fatsz = le16(bs + 22) ? le16(bs + 22) : le32(bs + 36);
if ((bps != 512 && bps != 1024 && bps != 2048 && bps != 4096) ||
bps < v->secsize || spc == 0 || (spc & (spc - 1)) || rsvd == 0 ||
nfats == 0 || nfats > 4 || fatsz == 0 || tot == 0 ||
(uint64)bps * spc > 64 * 1024)
return -1;
uint32 rootsecs = (rootents * 32 + bps - 1) / bps;
uint64 meta = rsvd + (uint64)nfats * fatsz + rootsecs;
if (meta >= tot)
return -1;
uint32 nclus = (uint32)((tot - meta) / spc);
// The type is decided by the cluster count and by nothing else -- not the
// label in the boot sector, which is a comment. This is the specification's
// own rule, and it is what makes a FAT16 volume that says "FAT32" FAT16.
v->fat = nclus < 4085 ? 12 : nclus < 65525 ? 16 : 32;
if (v->fat == 32 && le16(bs + 22) != 0)
return -1;
if (v->fat != 32 && rootents == 0)
return -1;
v->clsize = bps * spc;
v->nclus = nclus;
v->fat_bytes = (uint64)fatsz * bps;
v->fat_off = (uint64)rsvd * bps;
v->nfats = nfats;
v->root_off = v->fat_off + nfats * v->fat_bytes;
v->root_ents = v->fat == 32 ? 0 : rootents;
v->data_off = v->root_off + (uint64)rootsecs * bps;
v->free_count = FREE_UNKNOWN;
v->next_free = 2;
v->fsinfo_off = 0;
v->state_off = 0;
// A table bigger than the volume, or one too small for its clusters, is
// not a table to write into.
uint64 need = v->fat == 32 ? (uint64)(nclus + 2) * 4 :
v->fat == 16 ? (uint64)(nclus + 2) * 2 : ((uint64)(nclus + 2) * 3 + 1) / 2;
if (need > v->fat_bytes || v->data_off + (uint64)nclus * v->clsize > v->bytes)
return -1;
if (v->fat == 32) {
uint32 ext = le16(bs + 40);
v->root_clus = le32(bs + 44);
if (!valid_clus(v, v->root_clus))
return -1;
// Mirroring off: only the active copy is live, and only it is written.
if (ext & 0x80) {
v->fat_off += (ext & 0x0F) * v->fat_bytes;
v->nfats = 1;
}
uint32 fsi = le16(bs + 48);
uchar fi[512];
if (fsi && fsi < rsvd && vio(v, (uint64)fsi * bps, fi, 512, 0) == 0 &&
le32(fi) == 0x41615252 && le32(fi + 484) == 0x61417272) {
v->fsinfo_off = (uint64)fsi * bps;
if (le32(fi + 488) <= nclus)
v->free_count = le32(fi + 488);
if (valid_clus(v, le32(fi + 492)))
v->next_free = le32(fi + 492);
}
if (bs[66] == 0x29)
v->state_off = 65;
} else if (bs[38] == 0x29) {
v->state_off = 37;
}
return 0;
}
int
fat_kmount(uint bdev, int readonly)
{
uint32 secsize = 0;
uint64 nsec = blkdev_sectors(bdev, &secsize);
int slot = -1;
if (nsec == 0)
return -ENODEV;
if (!slotinit) {
initlock(&slotlk, "fatslots");
slotinit = 1;
}
acquire(&slotlk);
for (int i = 0; i < FAT_VOL_SLOTS; i++) {
if (vols[i].used && vols[i].bdev == bdev) {
release(&slotlk);
return -EBUSY;
}
if (!vols[i].used && slot < 0)
slot = i;
}
if (slot < 0) {
release(&slotlk);
return -EBUSY;
}
struct fvol *v = &vols[slot];
memset(v, 0, sizeof(*v));
v->used = 2; // claimed, not yet usable
release(&slotlk);
initsleeplock(&v->lk, "fat");
v->bdev = bdev;
v->secsize = secsize;
v->bytes = nsec * secsize;
v->ro = readonly;
if (probe(v) < 0) {
release_lines(v);
v->used = 0;
return -ENODEV;
}
uchar st = 0;
if (v->state_off && vio(v, v->state_off, &st, 1, 0) == 0 && (st & 1))
cprintf("fat: %s was not cleanly unmounted; some data may be damaged. "
"Run fsck.fat on it.\n", blkdev_name(bdev));
mark(v, 1);
flush(v, 1);
v->used = 1;
kinfo("fat: %s: FAT%d, %d KB clusters, %d MB%s\n", blkdev_name(bdev),
v->fat, (int)(v->clsize / 1024),
(int)(((uint64)v->nclus * v->clsize) >> 20), v->ro ? ", read-only" : "");
return FAT_DEV + slot;
}
int
fat_kumount(uint dev)
{
struct fvol *v = vol_of(dev);
if (!v)
return -EINVAL;
acquiresleep(&v->lk);
write_fsinfo(v);
mark(v, 0);
flush(v, 1);
release_lines(v);
v->nmap = 0;
releasesleep(&v->lk);
v->used = 0;
return 0;
}
int
fat_readonly(uint dev)
{
struct fvol *v = vol_of(dev);
return v ? v->ro : 0;
}
void
fat_ksync_all(int final)
{
for (int i = 0; i < FAT_VOL_SLOTS; i++) {
struct fvol *v = vol_of(FAT_DEV + i);
if (!v || v->ro)
continue;
acquiresleep(&v->lk);
write_fsinfo(v);
if (final)
mark(v, 0);
flush(v, 1);
releasesleep(&v->lk);
}
}
int
fat_kstatfs(uint dev, uint64 *bsize, uint64 *total, uint64 *freec)
{
struct fvol *v = vol_of(dev);
if (!v)
return -1;
acquiresleep(&v->lk);
if (v->free_count == FREE_UNKNOWN) {
v->free_count = count_free(v);
v->info_dirty = 1;
}
*bsize = v->clsize;
*total = v->nclus;
*freec = v->free_count;
releasesleep(&v->lk);
return 0;
}