275 lines
8.0 KiB
C
275 lines
8.0 KiB
C
/*
|
|
* Copyright (c) 2015 Steve White
|
|
*
|
|
* Use of this source code is governed by a MIT-style
|
|
* license that can be found in the LICENSE file or at
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#include <lk/err.h>
|
|
#include <lib/bio.h>
|
|
#include <lib/fs.h>
|
|
#include <lk/trace.h>
|
|
#include <malloc.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <lk/debug.h>
|
|
|
|
#include "fat_fs.h"
|
|
#include "fat32_priv.h"
|
|
|
|
#define DIR_ENTRY_LENGTH 32
|
|
#define USE_CACHE 1
|
|
|
|
static uint32_t fat32_next_cluster_in_chain(fat_fs_t *fat, uint32_t cluster) {
|
|
uint32_t fat_sector = (cluster) >> 7;
|
|
uint32_t fat_index = (cluster ) & 127;
|
|
|
|
uint32_t bnum = (fat->lba_start / fat->bytes_per_sector) + (fat->reserved_sectors + fat_sector);
|
|
uint32_t next_cluster = 0x0fffffff;
|
|
|
|
#if USE_CACHE
|
|
void *cache_ptr;
|
|
int err = bcache_get_block(fat->cache, &cache_ptr, bnum);
|
|
if (err < 0) {
|
|
printf("bcache_get_block returned: %i\n", err);
|
|
} else {
|
|
if (fat->fat_bits == 32) {
|
|
uint32_t *table = (uint32_t *)cache_ptr;
|
|
next_cluster = table[fat_index];
|
|
LE32SWAP(next_cluster);
|
|
} else if (fat->fat_bits == 16) {
|
|
uint16_t *table = (uint16_t *)cache_ptr;
|
|
next_cluster = table[fat_index];
|
|
LE16SWAP(next_cluster);
|
|
if (next_cluster > 0xfff0) {
|
|
next_cluster |= 0x0fff0000;
|
|
}
|
|
}
|
|
|
|
bcache_put_block(fat->cache, bnum);
|
|
}
|
|
#else
|
|
uint32_t offset = (bnum * fat->bytes_per_sector) + (fat_index * (fat->fat_bits / 8));
|
|
bio_read(fat->dev, &next_cluster, offset, 4);
|
|
LE32SWAP(next_cluster);
|
|
#endif
|
|
return next_cluster;
|
|
}
|
|
|
|
static inline off_t fat32_offset_for_cluster(fat_fs_t *fat, uint32_t cluster) {
|
|
off_t cluster_begin_lba = fat->reserved_sectors + (fat->fat_count * fat->sectors_per_fat);
|
|
return fat->lba_start + (cluster_begin_lba + (cluster - 2) * fat->sectors_per_cluster) * fat->bytes_per_sector;
|
|
}
|
|
|
|
static char *fat32_dir_get_filename(uint8_t *dir, off_t offset, int lfn_sequences) {
|
|
int result_len = 1 + (lfn_sequences == 0 ? 12 : (lfn_sequences * 26));
|
|
char *result = malloc(result_len);
|
|
int j = 0;
|
|
memset(result, 0x00, result_len);
|
|
|
|
if (lfn_sequences == 0) {
|
|
// Ignore trailing spaces in filename and/or extension
|
|
int fn_len=8, ext_len=3;
|
|
for (int i=7; i>=0; i--) {
|
|
if (dir[offset + i] == 0x20) {
|
|
fn_len--;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
for (int i=10; i>=8; i--) {
|
|
if (dir[offset + i] == 0x20) {
|
|
ext_len--;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i=0; i<fn_len; i++) {
|
|
result[j++] = dir[offset + i];
|
|
}
|
|
if (ext_len > 0) {
|
|
result[j++] = '.';
|
|
for (int i=0; i<ext_len; i++) {
|
|
result[j++] = dir[offset + 8 + i];
|
|
}
|
|
}
|
|
} else {
|
|
// XXX: not unicode aware.
|
|
for (int sequence=1; sequence<=lfn_sequences; sequence++) {
|
|
for (int i=1; i<DIR_ENTRY_LENGTH; i++) {
|
|
int char_offset = (offset - (sequence * DIR_ENTRY_LENGTH)) + i;
|
|
if (dir[char_offset] != 0x00 && dir[char_offset] != 0xff) {
|
|
result[j++] = dir[char_offset];
|
|
}
|
|
|
|
if (i == 9) {
|
|
i = 13;
|
|
} else if (i == 25) {
|
|
i = 27;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
status_t fat32_open_file(fscookie *cookie, const char *path, filecookie **fcookie) {
|
|
fat_fs_t *fat = (fat_fs_t *)cookie;
|
|
status_t result = ERR_GENERIC;
|
|
|
|
uint8_t *dir = malloc(fat->bytes_per_cluster);
|
|
uint32_t dir_cluster = fat->root_cluster;
|
|
fat_file_t *file = NULL;
|
|
|
|
const char *ptr;
|
|
/* chew up leading slashes */
|
|
ptr = &path[0];
|
|
while (*ptr == '/') {
|
|
ptr++;
|
|
}
|
|
|
|
bool done = false;
|
|
do {
|
|
// XXX: use the cache!
|
|
bio_read(fat->dev, dir, fat32_offset_for_cluster(fat, dir_cluster), fat->bytes_per_cluster);
|
|
|
|
char *next_sep = strchr(ptr, '/');
|
|
if (next_sep) {
|
|
/* terminate the next component, giving us a substring */
|
|
*next_sep = 0;
|
|
} else {
|
|
/* this is the last component */
|
|
done = true;
|
|
}
|
|
|
|
uint32_t offset = 0;
|
|
uint32_t lfn_sequences = 0;
|
|
bool matched = false;
|
|
while (dir[offset] != 0x00 && offset < fat->bytes_per_cluster) {
|
|
if ( dir[offset] == 0xE5 /*deleted*/) {
|
|
offset += DIR_ENTRY_LENGTH;
|
|
continue;
|
|
} else if ((dir[offset + 0x0B] & 0x08)) {
|
|
if (dir[offset + 0x0B] == 0x0f) {
|
|
lfn_sequences++;
|
|
}
|
|
|
|
offset += DIR_ENTRY_LENGTH;
|
|
continue;
|
|
}
|
|
|
|
char *filename = fat32_dir_get_filename(dir, offset, lfn_sequences);
|
|
lfn_sequences = 0;
|
|
|
|
matched = (strnicmp(ptr, filename, strlen(filename)) == 0);
|
|
free(filename);
|
|
|
|
if (matched) {
|
|
uint16_t target_cluster = fat_read16(dir, offset + 0x1a);
|
|
if (done == true) {
|
|
file = malloc(sizeof(fat_file_t));
|
|
file->fat_fs = fat;
|
|
file->start_cluster = target_cluster;
|
|
file->length = fat_read32(dir, offset + 0x1c);
|
|
file->attributes = dir[0x0B + offset];
|
|
result = NO_ERROR;
|
|
} else {
|
|
dir_cluster = target_cluster;
|
|
}
|
|
break;
|
|
}
|
|
|
|
offset += DIR_ENTRY_LENGTH;
|
|
}
|
|
|
|
if (matched == true) {
|
|
if (done == true) {
|
|
break;
|
|
} else {
|
|
/* move to the next separator */
|
|
ptr = next_sep + 1;
|
|
|
|
/* consume multiple separators */
|
|
while (*ptr == '/') {
|
|
ptr++;
|
|
}
|
|
}
|
|
} else {
|
|
// XXX: untested!!!
|
|
dir_cluster = fat32_next_cluster_in_chain(fat, dir_cluster);
|
|
if (dir_cluster == 0x0fffffff) {
|
|
// no more clusters in the chain
|
|
break;
|
|
}
|
|
}
|
|
} while (true);
|
|
|
|
out:
|
|
*fcookie = (filecookie *)file;
|
|
free(dir);
|
|
return result;
|
|
}
|
|
|
|
ssize_t fat32_read_file(filecookie *fcookie, void *buf, off_t offset, size_t len) {
|
|
fat_file_t *file = (fat_file_t *)fcookie;
|
|
fat_fs_t *fat = file->fat_fs;
|
|
bdev_t *dev = fat->dev;
|
|
|
|
uint32_t cluster = 0;
|
|
if (offset <= fat->bytes_per_cluster) {
|
|
cluster = file->start_cluster;
|
|
} else {
|
|
// XXX: support non-0 offsets
|
|
TRACE;
|
|
return -1;
|
|
}
|
|
|
|
uint32_t length = file->length;
|
|
uint32_t amount_read = 0;
|
|
|
|
do {
|
|
off_t lba_addr = fat32_offset_for_cluster(fat, cluster);
|
|
|
|
uint32_t to_read = fat->bytes_per_cluster;
|
|
uint32_t next_cluster = 0;
|
|
while ((next_cluster = fat32_next_cluster_in_chain(fat, cluster)) == cluster + 1) {
|
|
cluster = next_cluster;
|
|
to_read += fat->bytes_per_cluster;
|
|
}
|
|
cluster = next_cluster;
|
|
|
|
to_read = MIN(length - amount_read, to_read);
|
|
// XXX: support non-0 offsets
|
|
int err = bio_read(dev, buf+amount_read, lba_addr, to_read);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
amount_read += to_read;
|
|
|
|
if (amount_read < length) {
|
|
if (cluster == 0x0fffffff) {
|
|
printf("no more clusters, amount_read=%i, to_read=%i\n", amount_read, to_read);
|
|
break;
|
|
}
|
|
}
|
|
} while (amount_read < length);
|
|
|
|
return amount_read;
|
|
}
|
|
|
|
status_t fat32_close_file(filecookie *fcookie) {
|
|
fat_file_t *file = (fat_file_t *)fcookie;
|
|
free(file);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t fat32_stat_file(filecookie *fcookie, struct file_stat *stat) {
|
|
fat_file_t *file = (fat_file_t *)fcookie;
|
|
stat->size = file->length;
|
|
stat->is_dir = (file->attributes == fat_attribute_directory);
|
|
return NO_ERROR;
|
|
}
|