// 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; }