src/fs_vfat.c
#include "adt/hashtable.h"
#include "assert.h"
#include "block_cache.h"
#include "errno.h"
#include "hal.h"
#include "kmalloc.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "vfs.h"
#include "vmspace.h"
#ifdef DEBUG_vfat
# define dbg(args...) kprintf("vfat: " args)
#else
# define dbg(args...)
#endif
#define AREA_RESERVED 0
#define AREA_FAT 1
#define AREA_DATA 2
#define AREA_END 3
typedef struct vfat_filesystem {
unsigned ty;
uint32_t root_cluster;
uint32_t cluster_size;
uint32_t num_clusters;
uint32_t pointers_per_cluster;
uint32_t free_cluster_hint;
uint64_t areas[AREA_END+1];
block_device_t *dev;
disk_cache_t *cache;
void *cache_ptr;
uint64_t cached_address;
hashtable_t known_inos;
} vfat_filesystem_t;
typedef struct vfat_file {
vector_t clusters;
bool cluster_chain_read;
struct vfat_file *dir_file;
uint32_t dir_offset;
uintptr_t size;
uintptr_t first_free_dir_entry;
} vfat_file_t;
/* Clusters ************************************/
static unsigned char *read_cluster(vfat_filesystem_t *fs, uint32_t cluster, int area) {
const char *area_str;
switch (area) {
case AREA_RESERVED: area_str = "AREA_RESERVED"; break;
case AREA_FAT: area_str = "AREA_FAT"; break;
case AREA_DATA: area_str = "AREA_DATA"; break;
default: area_str = "<INVALID>";
}
dbg("read_cluster(%d, %s)\n", cluster, area_str);
/* Silence unused variable warnings in release mode. */
(void)area_str;
/* If it's difficult or you can't be bothered to implement some edge-case,
assume it can't happen ;) */
assert(fs->cluster_size <= get_page_size() && "More than one page per cluster not implemented yet!");
uint64_t address = fs->areas[area] + cluster * fs->cluster_size;
uint32_t offset = address & get_page_mask();
if (fs->cached_address != ~0ULL) {
disk_cache_release(fs->cache, fs->cached_address);
unmap((uintptr_t)fs->cache_ptr, 1);
}
disk_cache_get(fs->cache, address, fs->cache_ptr);
fs->cached_address = address;
return (unsigned char*)fs->cache_ptr + offset;
}
static uint32_t find_free_cluster(vfat_filesystem_t *fs) {
uint32_t cached_cluster = ~0U;
uint16_t *cache16 = NULL;
uint32_t *cache32 = NULL;
for (uint32_t i = fs->free_cluster_hint; i < fs->num_clusters; ++i) {
uint32_t cluster_num = i / fs->pointers_per_cluster;
uint32_t cluster_idx = i % fs->pointers_per_cluster;
if (cached_cluster != cluster_num) {
cache16 = (uint16_t*) read_cluster(fs, cluster_num, AREA_FAT);
cache32 = (uint32_t*) cache16;
cached_cluster = cluster_num;
}
assert(cache16 && cache32);
if ((fs->ty == 16 && cache16[cluster_idx] == 0) ||
(fs->ty == 32 && cache32[cluster_idx] == 0)) {
dbg("find_free_cluster() -> %d\n", i);
return i;
}
}
return ~0U;
}
/* Cluster chains ******************************/
static uintptr_t eoc_value(vfat_filesystem_t *fs) {
switch (fs->ty) {
default: assert(0); break;
case 12: return 0xFF8;
case 16: return 0xFFF8;
case 32: return 0x0FFFFF8;
}
}
static bool is_eoc(vfat_filesystem_t *fs, uint32_t cluster) {
return cluster >= eoc_value(fs);
}
static uint32_t get_next_cluster(vfat_filesystem_t *fs,
uint32_t cluster) {
assert(fs->ty != 12 && "FAT12 not supported!");
assert(cluster != 0);
unsigned idx = cluster % fs->pointers_per_cluster;
uint16_t *data16 = (uint16_t*) read_cluster(fs, cluster / fs->pointers_per_cluster, AREA_FAT);
uint32_t *data32 = (uint32_t*) data16;
unsigned ret;
if (fs->ty == 16)
ret = data16[idx];
else
ret = data32[idx] & 0x0FFFFFFF;
dbg("get_next_cluster(%x) -> %x\n", cluster, ret);
return ret;
}
static void set_next_cluster(vfat_filesystem_t *fs,
uint32_t cluster,
uint32_t next_cluster) {
assert(fs->ty != 12 && "FAT12 not supported!");
dbg("set_next_cluster(cluster=%#x, next_cluster=%#x)\n",
cluster, next_cluster);
unsigned idx = cluster % fs->pointers_per_cluster;
uint16_t *data16 = (uint16_t*) read_cluster(fs, cluster / fs->pointers_per_cluster, AREA_FAT);
uint32_t *data32 = (uint32_t*) data16;
if (fs->ty == 16)
data16[idx] = next_cluster & 0xFFFF;
else
data32[idx] = (data32[idx] & 0xF0000000) | (next_cluster & 0x0FFFFFFF);
}
static void read_cluster_chain(vfat_filesystem_t *fs, vfat_file_t *file) {
assert(!file->cluster_chain_read);
assert(vector_length(&file->clusters) == 1);
dbg("read_cluster_chain() start\n");
uintptr_t cluster = *(uintptr_t*) vector_get(&file->clusters, 0);
while (!is_eoc(fs, cluster)) {
dbg(" cluster: %d\n", cluster);
cluster = get_next_cluster(fs, cluster);
if (!is_eoc(fs, cluster))
vector_add(&file->clusters, &cluster);
}
file->cluster_chain_read = true;
dbg("read_cluster_chain() end\n");
}
static void write_cluster_chain(vfat_filesystem_t *fs, vfat_file_t *file) {
assert(file->cluster_chain_read);
for (unsigned i = 0; i < vector_length(&file->clusters); ++i) {
uintptr_t cluster = *(uintptr_t*) vector_get(&file->clusters, i);
uintptr_t next_cluster = (i != vector_length(&file->clusters) - 1) ?
*(uintptr_t*)vector_get(&file->clusters, i+1) : eoc_value(fs);
set_next_cluster(fs, cluster, next_cluster);
}
}
/* Dates ***************************************/
static uint64_t to_unix_time(uint16_t date, uint16_t time) {
unsigned day_of_month = date & 0x1F;
unsigned month_of_year = (date >> 5) & 0x0F;
unsigned year = (date >> 9) + 1980;
unsigned seconds = (time & 0x1F) * 2;
unsigned minutes = (time >> 5) & 0x3F;
unsigned hours = (time >> 11) & 0x1F;
return to_unix_timestamp(day_of_month, month_of_year, year,
seconds, minutes, hours);
}
static void from_unix_time(uint64_t ts, uint16_t *date, uint16_t *time) {
unsigned day_of_month, month_of_year, year;
unsigned seconds, minutes, hours;
from_unix_timestamp(ts, &day_of_month, &month_of_year, &year,
&seconds, &minutes, &hours);
*date = day_of_month & 0x1F;
*date |= (month_of_year & 0x0F) << 5;
*date |= (year - 1980) << 9;
*time = (seconds / 2) & 0x1F;
*time |= (minutes & 0x3F) << 5;
*time |= (hours & 0x1F) << 11;
}
/* Data ****************************************/
#define ATTR_READ_ONLY 0x01
#define ATTR_HIDDEN 0x02
#define ATTR_SYSTEM 0x04
#define ATTR_VOLUME_ID 0x08
#define ATTR_DIRECTORY 0x10
#define ATTR_ARCHIVE 0x20
src/fs_vfat.c
#define ATTR_LFN 0x0F
/* A directory in the FAT filesystem. */
typedef struct vfat_dir {
char name[11]; /* 8.3 filename */
uint8_t attributes;
uint8_t reserved;
uint8_t ctime_tenths;
uint16_t ctime;
uint16_t cdate;
uint16_t adate;
uint16_t cluster_hi;
uint16_t mtime;
uint16_t mdate;
uint16_t cluster_lo;
uint32_t size;
} __attribute__((packed)) vfat_dir_t;
/* A long-filename pseudo-directory. */
typedef struct vfat_lfn {
uint8_t order;
uint16_t name_1[5]; /* Characters 0..4 */
uint8_t attribute;
uint8_t type;
uint8_t checksum;
uint16_t name_2[6]; /* Characters 5..10 */
uint16_t zero;
uint16_t name_3[2]; /* Characters 11..12 */
} __attribute__((packed)) vfat_lfn_t;
static int64_t write(vfat_filesystem_t *vfs, vfat_file_t *file, uint64_t offset,
void *buf, uint64_t sz, bool update_attributes) {
unsigned char *cbuf = (unsigned char*) buf;
uint64_t sz_to_write = sz;
bool clusters_modified = false;
if (!file->cluster_chain_read)
read_cluster_chain(vfs, file);
/* Update accessed time and modified time. */
if (update_attributes && file->dir_file) {
uint16_t date, time;
from_unix_time(get_timestamp(), &date, &time);
write(vfs, file->dir_file,
file->dir_offset + offsetof(vfat_dir_t, adate), &date, 2, false);
write(vfs, file->dir_file,
file->dir_offset + offsetof(vfat_dir_t, mdate), &date, 2, false);
write(vfs, file->dir_file,
file->dir_offset + offsetof(vfat_dir_t, mtime), &time, 2, false);
if (sz + offset > file->size) {
file->size = sz + offset;
write(vfs, file->dir_file,
file->dir_offset + offsetof(vfat_dir_t, size),
&file->size, 4, false);
}
}
while (sz > 0) {
unsigned cluster_num = offset / vfs->cluster_size;
unsigned cluster_offs = offset % vfs->cluster_size;
unsigned cluster_avail = vfs->cluster_size - cluster_offs;
unsigned write_sz = (sz >= cluster_avail) ? cluster_avail : sz;
if (cluster_num >= vector_length(&file->clusters)) {
clusters_modified = true;
uint32_t c = find_free_cluster(vfs);
if (c == ~0U) {
dbg("find_free_cluster returned failure!");
set_errno(ENOSPC);
return sz_to_write - sz;
}
vector_add(&file->clusters, &c);
}
unsigned char *data = read_cluster(vfs,
*(uintptr_t*)vector_get(&file->clusters, cluster_num) - 2,
AREA_DATA);
memcpy(data+cluster_offs, cbuf, write_sz);
offset += write_sz;
sz -= write_sz;
cbuf += write_sz;
}
if (clusters_modified)
write_cluster_chain(vfs, file);
return (int64_t)sz_to_write;
}
static int64_t read(vfat_filesystem_t *vfs, vfat_file_t *file, uint64_t offset,
void *buf, uint64_t sz) {
unsigned char *cbuf = (unsigned char*) buf;
uint64_t max_read_sz = file->size - offset;
uint64_t sz_to_read = sz = (sz > max_read_sz) ? max_read_sz : sz;
dbg("read: offset %d size %d clusters[0] %d\n", offset, sz,
* (uintptr_t*) vector_get(&file->clusters, 0));
if (!file->cluster_chain_read)
read_cluster_chain(vfs, file);
/* Update accessed time. */
uint16_t date, time;
from_unix_time(get_timestamp(), &date, &time);
if (file->dir_file)
write(vfs, file->dir_file,
file->dir_offset + offsetof(vfat_dir_t, adate), &date, 2, false);
while (sz > 0) {
unsigned cluster_num = offset / vfs->cluster_size;
unsigned cluster_offs = offset % vfs->cluster_size;
unsigned cluster_avail = vfs->cluster_size - cluster_offs;
unsigned read_sz = (sz >= cluster_avail) ? cluster_avail : sz;
unsigned cluster = *(uintptr_t*)vector_get(&file->clusters, cluster_num) - 2;
unsigned char *data = read_cluster(vfs,
cluster,
AREA_DATA);
memcpy(cbuf, data+cluster_offs, read_sz);
offset += read_sz;
sz -= read_sz;
cbuf += read_sz;
}
return (int64_t)sz_to_read;
}
/* Directories *********************************/
static vfat_file_t *make_file_t(uint32_t cluster,
vfat_file_t *parent_dir,
uint32_t parent_offset,
uint32_t size) {
vfat_file_t *file = kmalloc(sizeof(vfat_file_t));
file->clusters = vector_new(sizeof(uintptr_t), 8);
vector_add(&file->clusters, &cluster);
file->cluster_chain_read = false;
file->dir_file = parent_dir;
file->dir_offset = parent_offset;
file->size = size;
return file;
}
static uintptr_t cache_dir_size(vfat_filesystem_t *fs, vfat_file_t *dir) {
if (!dir->cluster_chain_read)
read_cluster_chain(fs, dir);
dir->size = fs->cluster_size * vector_length(&dir->clusters);
return dir->size;
}
static uint32_t add_to_directory(vfat_filesystem_t *fs, vfat_file_t *dir, vector_t *entries) {
dbg("add_to_directory(nentries=%d, first_free=%d)\n", vector_length(entries), dir->first_free_dir_entry);
vfat_dir_t *dirs = kmalloc(sizeof(vfat_dir_t) * vector_length(entries));
for (unsigned i = 0; i < vector_length(entries); ++i)
dirs[i] = *(vfat_dir_t*)vector_get(entries, i);
uint32_t offs = dir->first_free_dir_entry;
write(fs, dir, offs, dirs,
sizeof(vfat_dir_t) * vector_length(entries), true);
dir->first_free_dir_entry += sizeof(vfat_dir_t) * vector_length(entries);
kfree(dirs);
return dir->first_free_dir_entry - sizeof(vfat_dir_t);
}
static vector_t read_directory(vfat_filesystem_t *fs, vfat_file_t *node) {
dbg("read_directory(cluster[0] = %d)\n", *(uintptr_t*)vector_get(&node->clusters, 0));
vector_t entries = vector_new(sizeof(dirent_t), 4);
unsigned char *buf = kmalloc(4096);
uintptr_t offset = 0;
vector_t name = vector_new(2, 16);
uint64_t sz_read;
bool cont = true;
do {
sz_read = read(fs, node, offset, buf, 4096);
for (unsigned idx = 0; idx < sz_read; idx += sizeof(vfat_dir_t)) {
vfat_dir_t *dir = (vfat_dir_t*) &buf[idx];
if ((unsigned char)dir->name[0] == 0xE5)
/* Entry does not exist (but there are more entries after this). */
continue;
if (dir->name[0] == 0x00) {
/* Entry does not exist, and there are no more entries. */
node->first_free_dir_entry = offset + idx;
cont = false;
break;
}
if (dir->attributes == ATTR_LFN) {
/* Long filename entry. */
vfat_lfn_t *lfn = (vfat_lfn_t*) dir;
vector_add_multiple(&name, lfn->name_1, 5);
vector_add_multiple(&name, lfn->name_2, 6);
vector_add_multiple(&name, lfn->name_3, 2);
continue;
}
uintptr_t data_cluster = dir->cluster_lo | (dir->cluster_hi << 16);
char *n;
/* If we had a LFN entry, convert from utf16. */
if (vector_length(&name) > 0) {
uint16_t *n_utf16 = (uint16_t*) vector_get_data(&name);
n = kmalloc(vector_length(&name) * 2);
vector_drop(&name);
utf16_to_utf8((uint8_t*)n, n_utf16);
} else {
n = kmalloc(12);
memcpy(n, dir->name, 11);
n[11] = '\0';
}
/* The name may be right-padded with spaces. */
while (n[strlen(n) - 1] == ' ')
n[strlen(n) - 1] = '\0';
dirent_t dent;
dent.name = n;
/* Have we seen this cluster before? */
inode_t *ino = hashtable_get(&fs->known_inos, (void*)data_cluster);
if (ino) {
dent.ino = ino;
++ino->nlink;
vector_add(&entries, &dent);
continue;
}
/* FIXME: Use a slab. */
ino = kmalloc(sizeof(inode_t));
ino->type = (dir->attributes == ATTR_DIRECTORY) ? it_dir : it_file;
/* Copy the default permissions given by Linux. */
ino->mode = (dir->attributes == ATTR_DIRECTORY) ? 040755 : 0100755;
ino->nlink = 1;
ino->uid = ino->gid = ino->handles = 0;
ino->u.dir_cache = 0;
rwlock_init(&ino->rwlock);
ino->atime = to_unix_time(dir->adate, 0);
ino->ctime = to_unix_time(dir->cdate, dir->ctime);
ino->mtime = to_unix_time(dir->mdate, dir->mtime);
vfat_file_t *file = make_file_t(data_cluster, node, idx, dir->size);
ino->data = file;
ino->size = (dir->attributes == ATTR_DIRECTORY) ?
cache_dir_size(fs, file) : dir->size;
dent.ino = ino;
vector_add(&entries, &dent);
hashtable_set(&fs->known_inos, (void*)data_cluster, ino);
}
offset += sz_read;
} while (cont && sz_read == 4096);
vector_destroy(&name);
kfree(buf);
return entries;
}
/* Header/Initialisation ***********************/
typedef struct vfat_header {
uint8_t header[3]; /* Should be 6B 3C 90 (JMP 3C; NOP). Irrelevant. */
uint8_t identifier[8];
uint16_t bytes_per_sector;
uint8_t sectors_per_cluster;
uint16_t reserved_sectors;
uint8_t num_fats;
uint16_t num_dir_entries;
uint16_t num_sectors; /* If 0, this value is stored in 'large_num_sectors'. */
uint8_t media_desc_type;
uint16_t sectors_per_fat16;
uint16_t sector_per_track;
uint16_t num_heads;
uint32_t hidden_sectors;
uint32_t large_num_sectors;
The directory structure contains the file’s name in 8.3 format - for example MYEXEC~1.EXE.
Microsoft added the long file name extension late in FAT’s lifetime to get around this restriction. It works by having pseudo directory descriptors before a real descriptor, each defining 13 characters (in UTF-16) of the filename. It’s a pretty ugly hack.
If a directory entry has the attribute 0x0F, it should be treated as a long file name pseudo entry.
src/fs_vfat.c
union {
struct vfat_16_header {
uint8_t drive_number;
uint8_t flags;
uint8_t boot_signature;
uint32_t volume_id;
char volume_label[11];
char system_ident[8];
uint8_t bootcode[448];
uint16_t signature; /* Should be 0xAA55 */
} __attribute__((packed)) h16;
struct vfat_32_header {
uint32_t sectors_per_fat32;
uint16_t flags;
uint16_t version;
uint32_t root_cluster;
uint16_t fsinfo_cluster;
uint16_t backup_boot_sector;
uint8_t reserved[12];
uint8_t drive_number;
uint8_t flags2;
uint8_t boot_signature;
uint32_t volume_id;
char volume_label[11];
char system_ident[8];
uint8_t bootcode[420];
uint16_t signature; /* Should be 0xAA55 */
} __attribute__((packed)) h32;
} u;
} __attribute__((packed)) vfat_header_t;
static bool read_bpb(block_device_t *dev, vfat_header_t *hdr) {
uint8_t *sector = (uint8_t*)vmspace_alloc(&kernel_vmspace, 0x1000, 1);
if (dev->length(dev) < 512)
return NULL;
bool ret = true;
int nbytes = dev->read(dev, 0ULL, (void*)sector, 0x1000);
if (nbytes != 0x1000) {
dbg("unable to read from device (read returned %d)\n", nbytes);
ret = false;
} else if (sector[510] != 0x55 || sector[511] != 0xAA) {
ret = false;
} else {
memcpy((void*)hdr, sector, 512);
}
vmspace_free(&kernel_vmspace, 0x1000, (uintptr_t)sector, 1);
return ret;
}
static vfat_filesystem_t *probe(block_device_t *dev) {
vfat_header_t hdr;
if (!read_bpb(dev, &hdr)) {
dbg("probe failed: bpb read failed!\n");
return NULL;
}
vfat_filesystem_t *fs = (vfat_filesystem_t*) kmalloc(sizeof(vfat_filesystem_t));
/* The size of the volume is stored differently depending on FAT type. */
uint32_t num_sectors = (hdr.num_sectors > 0) ? hdr.num_sectors : hdr.large_num_sectors;
/* Similarly with the sectors_per_fat variable. */
uint32_t sectors_per_fat = (hdr.sectors_per_fat16) ? hdr.sectors_per_fat16 :
hdr.u.h32.sectors_per_fat32;
uint32_t first_data_sector = hdr.reserved_sectors + (hdr.num_fats * sectors_per_fat);
uint32_t num_data_sectors = num_sectors - first_data_sector;
fs->cluster_size = hdr.sectors_per_cluster * 512;
fs->root_cluster = hdr.u.h32.root_cluster;
fs->areas[AREA_RESERVED] = 0;
fs->areas[AREA_FAT] = hdr.reserved_sectors * 512;
fs->areas[AREA_DATA] = first_data_sector * 512;
fs->areas[AREA_END] = fs->areas[AREA_DATA] + num_data_sectors * 512;
/* FIXME: Read free cluster hint from fsinfo struct on FAT32. */
fs->free_cluster_hint = 0;
dbg("num_sectors %x sectors_per_fat %x first_data_sector %x\n",
num_sectors, sectors_per_fat, first_data_sector);
dbg("num_data_sectors %x cluster_size %x\n",
num_data_sectors, fs->cluster_size);
if (fs->cluster_size == 0) {
dbg("Cluster size was 0, bailing!\n");
kfree(fs);
return NULL;
}
fs->num_clusters = num_data_sectors / hdr.sectors_per_cluster;
if (fs->num_clusters < 4085)
fs->ty = 12;
else if (fs->num_clusters < 65525)
fs->ty = 16;
else
fs->ty = 32;
fs->pointers_per_cluster = fs->cluster_size / ( fs->ty == 32 ? 4 : 2 );
dbg("FAT%d partition detected\n", fs->ty);
fs->cache = disk_cache_new(disk_cache_group_get_default(), dev);
fs->cache_ptr = (void*)vmspace_alloc(&kernel_vmspace, 0x1000, 0);
fs->cached_address = ~0ULL;
fs->dev = dev;
fs->known_inos = hashtable_new(257);
return fs;
}
static int64_t vfat_read(filesystem_t *fs, inode_t *ino, uint64_t offset,
void *buf, uint64_t sz) {
vfat_filesystem_t *vfs = (vfat_filesystem_t*) fs->data;
return read(vfs, (vfat_file_t*)ino->data, offset, buf, sz);
}
static int64_t vfat_write(filesystem_t *fs, inode_t *ino, uint64_t offset,
void *buf, uint64_t sz) {
vfat_filesystem_t *vfs = (vfat_filesystem_t*) fs->data;
return write(vfs, (vfat_file_t*)ino->data, offset, buf, sz, true);
}
static vector_t vfat_readdir(filesystem_t *fs, inode_t *dir) {
assert(dir->type == it_dir && "readdir can only be called on a directory!");
vfat_filesystem_t *vfs = (vfat_filesystem_t*) fs->data;
return read_directory(vfs, (vfat_file_t*)dir->data);
}
static void populate_entries_for_lfn(vector_t *entries, const char *name, uint8_t checksum) {
uint16_t *name16 = kmalloc(strlen(name) * 2 + 1);
utf8_to_utf16(name16, (uint8_t*)name);
vector_t entries_reversed = vector_new(sizeof(vfat_lfn_t), 1);
/* Calculate the size of the string, *including the null terminator!* */
int sz = 0;
while (name16[sz++] != 0)
;
int i = 0;
while (i < sz+1) {
/* Can store 11 16-bit characters per dirent. */
/* If we've only got the NULL character left to insert, we don't
need to create a new dirent for it. */
if (i == sz) {
assert(name16[i] == 0 && "Expected U+0000 terminator!");
break;
}
vfat_lfn_t ent;
memset(&ent, 0, sizeof(vfat_lfn_t));
ent.attribute = ATTR_LFN;
ent.checksum = checksum;
/* Pad with 0xFFFF. */
unsigned j;
for (j = 0; j < 5; ++j)
ent.name_1[j] = 0xFFFF;
for (j = 0; j < 6; ++j)
ent.name_2[j] = 0xFFFF;
for (j = 0; j < 2; ++j)
ent.name_3[j] = 0xFFFF;
for (j = 0; j < 5 && i < sz; ++i,++j)
ent.name_1[j] = name16[i];
for (j = 0; j < 6 && i < sz; ++i,++j)
ent.name_2[j] = name16[i];
for (j = 0; j < 2 && i < sz; ++i,++j)
ent.name_3[j] = name16[i];
vector_add(&entries_reversed, &ent);
}
/* The LFN entries are actually written in reverse order. */
for (i = vector_length(&entries_reversed)-1; i >= 0; --i) {
vfat_lfn_t *ent = vector_get(&entries_reversed, i);
ent->order = i+1;
if (i == 0) ent->order |= 0x40;
vector_add(entries, ent);
}
kfree(name16);
vector_destroy(&entries_reversed);
}
static void populate_8_11_entry(vfat_dir_t *ent, const char *name) {
/* Separate out the filename and extension, if applicable. */
unsigned idx = 0;
bool found_dot = false;
/* Pad with spaces. */
memset(ent->name, (uint8_t)' ', 11);
for (unsigned i = 0; i < strlen(name); ++i) {
/* Skip over non-ASCII chars. */
if (name[i] < 0) continue;
if (idx < 8 && !found_dot)
ent->name[idx] = name[i];
else if (idx >= 8 && idx < 11 && found_dot)
ent->name[idx] = name[i];
else if (!found_dot && name[i] == '.')
found_dot = true;
}
}
static uint8_t calculate_lfn_checksum(vfat_dir_t *ent) {
uint8_t sum = 0;
for (unsigned i = 0; i < 11; ++i)
sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1) + ent->name[i];
return sum;
}
static int vfat_mknod(filesystem_t *fs, inode_t *dir_ino, inode_t *dest_ino,
const char *name) {
vfat_filesystem_t *vfs = (vfat_filesystem_t*) fs->data;
/* FAT filesystems can only contain files and directories. */
if (dest_ino->type != it_file && dest_ino->type != it_dir) {
set_errno(EINVAL);
return -1;
}
uint16_t date, time;
from_unix_time(get_timestamp(), &date, &time);
vfat_dir_t main_entry;
memset(&main_entry, 0, sizeof(vfat_dir_t));
populate_8_11_entry(&main_entry, name);
vector_t entries = vector_new(sizeof(vfat_dir_t), 2);
populate_entries_for_lfn(&entries, name, calculate_lfn_checksum(&main_entry));
main_entry.attributes = (dest_ino->type == it_dir) ? ATTR_DIRECTORY : 0;
main_entry.cdate = main_entry.mdate = main_entry.adate = date;
main_entry.ctime = main_entry.mtime = time;
/* The entry (directory or file) must have at least one cluster.
Find a cluster and set it to all zeroes (so that it is valid as a
directory). */
uint32_t cluster = find_free_cluster(vfs);
if (cluster == ~0U) {
dbg("find_free_cluster returned failure!");
set_errno(ENOSPC);
return -1;
}
unsigned char *data = read_cluster(vfs, cluster-2, AREA_DATA);
memset(data, 0, vfs->cluster_size);
set_next_cluster(vfs, cluster, eoc_value(vfs));
main_entry.cluster_lo = cluster & 0xFFFF;
main_entry.cluster_hi = cluster >> 16;
vector_add(&entries, &main_entry);
uint32_t offset = add_to_directory(vfs, (vfat_file_t*) dir_ino->data, &entries);
vector_destroy(&entries);
vfat_file_t *file = make_file_t(cluster, (vfat_file_t*) dir_ino->data, offset, 0);
dest_ino->data = file;
return 0;
}
static int vfat_get_root(filesystem_t *fs, inode_t *ino) {
vfat_filesystem_t *vfs = (vfat_filesystem_t*) fs->data;
uintptr_t cluster = (vfs->ty == 32) ? vfs->root_cluster : 0;
/* FIXME: How do we know the size of the root cluster? */
ino->data = make_file_t(cluster, NULL, 0, 512);
cache_dir_size(vfs, (vfat_file_t*)ino->data);
ino->type = it_dir;
ino->mode = 0777;
ino->nlink = 1;
ino->uid = ino->gid = ino->handles = 0;
ino->u.dir_cache = 0;
hashtable_set(&vfs->known_inos, (void*)cluster, ino);
hashtable_set(&vfs->known_inos, (void*)0, ino);
return 0;
}
static void vfat_destroy(filesystem_t *fs) {
vfat_filesystem_t *vfs = (vfat_filesystem_t*) fs->data;
if (vfs->cached_address != ~0ULL) {
disk_cache_release(vfs->cache, vfs->cached_address);
unmap((uintptr_t)vfs->cache_ptr, 1);
}
disk_cache_destroy(vfs->cache);
}
static filesystem_t fs_vfat = {
.read = &vfat_read,
.write = &vfat_write,
.readdir = &vfat_readdir,
.mknod = &vfat_mknod,
.get_root = &vfat_get_root,
.destroy = &vfat_destroy
};
static int vfat_probe(dev_t dev, filesystem_t *fs) {
block_device_t *bdev = get_block_device(dev);
if (!bdev) return 1;
vfat_filesystem_t *vfs = probe(bdev);
if (!vfs) return 1;
memcpy(fs, &fs_vfat, sizeof(filesystem_t));
fs->data = vfs;
return 0;
}
static int vfat_init() {
assert(register_filesystem("vfat", &vfat_probe) == 0);
return 0;
}
static prereq_t req[] = { {"vfs",NULL}, {"kmalloc",NULL}, {NULL,NULL} };
static module_t x run_on_startup = {
.name = "fs_vfat",
.required = req,
.load_after = NULL,
.init = &vfat_init,
.fini = NULL
};
The FAT header differs depending on type - FAT12 and FAT16 have one set of fields, FAT32 has a different set.