From 6696ab7fd9ec9e25b7ceb542faace2f5da5f718b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 31 May 2015 02:42:11 +0200 Subject: [PATCH] source import --- .gitignore | 3 + Makefile | 7 + fat16.c | 462 ++++++++++++++++++++++++++++++++++++++++++++++++ fat16.h | 186 +++++++++++++++++++ imgs/.gitignore | 2 + main.c | 103 +++++++++++ 6 files changed, 763 insertions(+) create mode 100644 Makefile create mode 100644 fat16.c create mode 100644 fat16.h create mode 100644 imgs/.gitignore create mode 100644 main.c diff --git a/.gitignore b/.gitignore index bbf313b..cf73380 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ # Debug files *.dSYM/ + + +test \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f10f027 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +all: main + +main: main.c + gcc -std=gnu99 main.c fat16.c -o test -g + +run: main + ./test diff --git a/fat16.c b/fat16.c new file mode 100644 index 0000000..f5f8d1b --- /dev/null +++ b/fat16.c @@ -0,0 +1,462 @@ +#include +#include +#include +#include + +#include "fat16.h" + + +char* fat16_volume_label(const FAT16* fat, char* str) +{ + FAT16_FILE first; + fat16_open_root(fat, &first); + + if (first.type == FT_LABEL) { + return fat16_display_name(&first, str); + } + + // find where spaces end + uint8_t j = 10; + for (; j >= 0; j--) + { + if (fat->bs.volume_label[j] != ' ') break; + } + + // copy all until spaces + uint8_t i; + for (i = 0; i <= j; i++) + { + str[i] = fat->bs.volume_label[i]; + } + + str[i] = 0; // ender + + return str; +} + + +/** + * Resolve a file name, trim spaces and add null terminator. + * Returns the passed char*, or NULL on error. + */ +char* fat16_display_name(const FAT16_FILE* file, char* str) +{ + // Cannot get name for special files + if (file->type == FT_NONE || // not-yet-used directory location + file->type == FT_DELETED || // deleted file entry + file->attribs == 0x0F) // long name special entry (system, hidden) + return NULL; + + // find first non-space + uint8_t j = 7; + for (; j >= 0; j--) + { + if (file->name[j] != ' ') break; + } + + // j ... last no-space char + + uint8_t i; + for (i = 0; i <= j; i++) + { + str[i] = file->name[i]; + } + + + // directory entry, no extension + if (file->type == FT_SUBDIR || file->type == FT_SELF || file->type == FT_PARENT) + { + str[i] = 0; // end of string + return str; + } + + + // add a dot + if (file->type != FT_LABEL) // volume label has no dot! + str[i++] = '.'; + + // Add extension chars + for (j = 0; j < 3; j++, i++) + { + const char c = file->ext[j]; + if (c == ' ') break; + str[i] = c; + } + + str[i] = 0; // end of string + + return str; +} + + +/** Read boot sector from given address */ +void _fat16_read_bs(const BLOCKDEV* dev, Fat16BootSector* info, const uint32_t addr); + +/** + * Find absolute address of first BootSector. + * Returns 0 on failure. + */ +uint32_t _fat16_find_bs(const BLOCKDEV* dev); + + +/** Get cluster's starting address */ +uint32_t _fat16_clu_start(const FAT16* fat, const uint16_t cluster); + + +/** Find following cluster using FAT for jumps */ +uint16_t _fat16_next_clu(const FAT16* fat, uint16_t cluster); + + +/** Find relative address in a file, using FAT for cluster lookup */ +uint32_t _fat16_clu_add(const FAT16* fat, uint16_t cluster, uint32_t addr); + + +/** Read a file entry from directory (dir starting cluster, entry number) */ +void _fat16_fopen(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, const uint16_t num); + + + + +/** + * Find absolute address of first boot sector. + * Returns 0 on failure. + */ +uint32_t _fat16_find_bs(const BLOCKDEV* dev) +{ + // Reference structure: + // + // typedef struct __attribute__((packed)) { + // uint8_t first_byte; + // uint8_t start_chs[3]; + // uint8_t partition_type; + // uint8_t end_chs[3]; + // uint32_t start_sector; + // uint32_t length_sectors; + // } PartitionTable; + + uint16_t addr = 0x1BE + 4; // fourth byte of structure is the type. + uint32_t tmp = 0; + uint16_t tmp2; + + for (uint8_t i = 0; i < 4; i++, addr += 16) + { + // Read partition type + dev->aread(&tmp, 1, addr); + + // Check if type is valid + if (tmp == 4 || tmp == 6 || tmp == 14) + { + // read MBR address + dev->rseek(3);// skip 3 bytes + dev->read(&tmp, 4); + + tmp = tmp << 9; // multiply address by 512 (sector size) + + // Verify that the boot sector has a valid signature mark + dev->aread(&tmp2, 2, tmp + 510); + if (tmp2 != 0xAA55) continue; // continue to next entry + + // return absolute MBR address + return tmp; + } + } + + return 0; +} + + +void _fat16_read_bs(const BLOCKDEV* dev, Fat16BootSector* info, const uint32_t addr) +{ + dev->seek(addr + 13); // skip 13 + + dev->read(&(info->sectors_per_cluster), 6); // spc, rs, nf, re + + info->total_sectors = 0; + dev->read(&(info->total_sectors), 2); // short sectors + + dev->rseek(1); // md + + dev->read(&(info->fat_size_sectors), 2); + + dev->rseek(8); // spt, noh, hs + + // read or skip long sectors field + if (info->total_sectors == 0) + { + dev->read(&(info->total_sectors), 4); + } else + { + dev->rseek(4); // tsl + } + + dev->rseek(7); // dn, ch, bs, vi + + dev->read(&(info->volume_label), 11); +} + + +/** Initialize a FAT16 handle */ +void fat16_init(const BLOCKDEV* dev, FAT16* fat) +{ + const uint32_t bs_a = _fat16_find_bs(dev); + fat->dev = dev; + _fat16_read_bs(dev, &(fat->bs), bs_a); + fat->fat_addr = bs_a + (fat->bs.reserved_sectors * 512); + fat->rd_addr = bs_a + (fat->bs.reserved_sectors + fat->bs.fat_size_sectors * fat->bs.num_fats) * 512; + fat->data_addr = fat->rd_addr + (fat->bs.root_entries * 32); // entry is 32B long + + fat->bs.bytes_per_cluster = (fat->bs.sectors_per_cluster * 512); +} + + +/** Get cluster starting address */ +uint32_t _fat16_clu_start(const FAT16* fat, const uint16_t cluster) +{ + if (cluster < 2) return fat->rd_addr; + return fat->data_addr + (cluster - 2) * fat->bs.bytes_per_cluster; +} + + +uint16_t _fat16_next_clu(const FAT16* fat, uint16_t cluster) +{ + fat->dev->aread(&cluster, 2, fat->fat_addr + (cluster * 2)); + return cluster; +} + + +/** Find file-relative address in fat table */ +uint32_t _fat16_clu_add(const FAT16* fat, uint16_t cluster, uint32_t addr) +{ + while (addr >= fat->bs.bytes_per_cluster) + { + cluster = _fat16_next_clu(fat, cluster); + if (cluster == 0xFFFF) return 0xFFFF; // fail + addr -= fat->bs.bytes_per_cluster; + } + + return _fat16_clu_start(fat, cluster) + addr; +} + + +/** Move file cursor to a position relative to file start */ +bool fat16_fseek(FAT16_FILE* file, uint32_t addr) +{ + // Clamp. + if (addr > file->size) + return false; + + // Store as rel + file->cur_rel = addr; + + // Rewind and resolve abs, clu, ofs + file->cur_clu = file->clu_start; + + while (addr >= file->fat->bs.bytes_per_cluster) + { + file->cur_clu = _fat16_next_clu(file->fat, file->cur_clu); + addr -= file->fat->bs.bytes_per_cluster; + } + + file->cur_abs = _fat16_clu_start(file->fat, file->cur_clu) + addr; + file->cur_ofs = addr; + + return true; +} + + +/** + * Read a file entry + * + * dir_cluster ... directory start cluster + * num ... entry number in the directory + */ +void _fat16_fopen(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, const uint16_t num) +{ + // Resolve starting address + uint32_t addr; + if (dir_cluster == 0) + { + addr = _fat16_clu_start(fat, dir_cluster) + num * 32; // root directory, max 512 entries. + } else + { + addr = _fat16_clu_add(fat, dir_cluster, num * 32); // cluster + N (wrapping to next cluster if needed) + } + + fat->dev->aread(file, 12, addr); + fat->dev->rseek(14); // skip 14 bytes + fat->dev->read(((void*)file) + 12, 6); // read remaining bytes + + file->clu = dir_cluster; + file->num = num; + + // Resolve filename & type + + file->type = FT_FILE; + + switch(file->name[0]) + { + case 0x00: + file->type = FT_NONE; + return; + + case 0xE5: + file->type = FT_DELETED; + return; + + case 0x05: // Starting with 0xE5 + file->type = FT_FILE; + file->name[0] = 0xE5; // convert to the real character + break; + + case 0x2E: + if (file->name[1] == 0x2E) + { + // ".." directory + file->type = FT_PARENT; + } else + { + // "." directory + file->type = FT_SELF; + } + break; + + default: + file->type = FT_FILE; + } + + // handle subdir, label + if (file->attribs & FA_DIR && file->type == FT_FILE) + { + file->type = FT_SUBDIR; + } else + if (file->attribs == FA_LABEL) + { + file->type = FT_LABEL; // volume label special file + } else + if (file->attribs == 0x0F) + { + file->type = FT_LFN; // long name special file, can be safely ignored + } + + // add a FAT pointer + file->fat = fat; + + // Init cursors + fat16_fseek(file, 0); +} + + +/** + * Check if file is a valid entry (to be shown) + */ +bool fat16_is_file_valid(const FAT16_FILE* file) +{ + switch (file->type) { + case FT_FILE: + case FT_SUBDIR: + case FT_SELF: + case FT_PARENT: + return true; + + default: + return false; + } +} + + + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +bool fat16_fread(FAT16_FILE* file, void* target, uint32_t len) +{ + if (file->cur_abs == 0xFFFF) + return false; // file at the end already + + if (file->cur_rel + len > file->size) + return false; // attempt to read outside file size + + while (len > 0 && file->cur_rel < file->size) + { + uint16_t chunk = MIN(file->size - file->cur_rel, MIN(file->fat->bs.bytes_per_cluster - file->cur_ofs, len)); + + file->fat->dev->aread(target, chunk, file->cur_abs); + + file->cur_abs += chunk; + file->cur_rel += chunk; + file->cur_ofs += chunk; + + target += chunk; + + if (file->cur_ofs >= file->fat->bs.bytes_per_cluster) + { + file->cur_clu = _fat16_next_clu(file->fat, file->cur_clu); + file->cur_abs = _fat16_clu_start(file->fat, file->cur_clu); + file->cur_ofs = 0; + } + + len -= chunk; + } + + return true; +} + + +/** Open next file in the directory */ +bool fat16_next(FAT16_FILE* file) +{ + if (file->clu == 0 && file->num >= file->fat->bs.root_entries) + return false; // attempt to read outside root directory. + + uint32_t addr = _fat16_clu_add(file->fat, file->clu, (file->num + 1) * 32); + if (addr == 0xFFFF) + return false; // next file is out of the directory cluster + + // read first byte of the file entry; if zero, can't read (file is NONE) + // FIXME this may be a problem when creating a new file... + uint8_t x; + file->fat->dev->aread(&x, 1, addr); + if (x == 0) + return false; + + _fat16_fopen(file->fat, file, file->clu, file->num+1); + +/* // Skip bad files + if (!fat16_is_file_valid(file)) + fat16_next(file);*/ + + return true; +} + + +/** Open previous file in the directory */ +bool fat16_prev(FAT16_FILE* file) +{ + if (file->num == 0) + return false; // first file already + + _fat16_fopen(file->fat, file, file->clu, file->num-1); + +/* // Skip bad files + if (!fat16_is_file_valid(file)) + fat16_prev(file);*/ + + return true; +} + + +/** Open a directory */ +bool fat16_opendir(FAT16_FILE* file) +{ + // Don't open non-dirs and "." directory. + if (!(file->attribs & FA_DIR) || file->type == FT_SELF) + return false; + + _fat16_fopen(file->fat, file, file->clu_start, 0); + return true; +} + + +void fat16_open_root(const FAT16* fat, FAT16_FILE* file) +{ + _fat16_fopen(fat, file, 0, 0); +} diff --git a/fat16.h b/fat16.h new file mode 100644 index 0000000..10affd3 --- /dev/null +++ b/fat16.h @@ -0,0 +1,186 @@ +#pragma once + +/** Abstract block device interface */ +typedef struct { + // Sequential read + void (*read)(void* dest, const uint16_t len); + // Read at address + void (*aread)(void* dest, const uint16_t len, const uint32_t addr); + // Sequential write + void (*write)(const void* src, const uint16_t len); + // Write at address + void (*awrite)(const void* src, const uint16_t len, const uint32_t addr); + // Absolute seek + void (*seek)(const uint32_t); + // Relative seek + void (*rseek)(const uint16_t); +} BLOCKDEV; + + +// ------------------------------- + +/** file types (values don't matter) */ +typedef enum { + FT_NONE = '-', + FT_DELETED = 'x', + FT_SUBDIR = 'D', + FT_PARENT = 'P', + FT_LABEL = 'L', + FT_LFN = '~', + FT_SELF = '.', + FT_FILE = 'F' +} FAT16_FT; + + +// File Attributes (bit flags) +#define FA_READONLY 0x01 // read only file +#define FA_HIDDEN 0x02 // hidden file +#define FA_SYSTEM 0x04 // system file +#define FA_LABEL 0x08 // volume label entry, found only in root directory. +#define FA_DIR 0x10 // subdirectory +#define FA_ARCHIVE 0x20 // archive flag + + +/** Boot Sector structure - INTERNAL! */ +typedef struct __attribute__((packed)) { + + // Fields loaded directly from disk: + + // 13 bytes skipped + uint8_t sectors_per_cluster; + uint16_t reserved_sectors; + uint8_t num_fats; + uint16_t root_entries; + // 3 bytes skipped + uint16_t fat_size_sectors; + // 8 bytes skipped + uint32_t total_sectors; // if "short size sectors" is used, it's copied here too + // 7 bytes skipped + char volume_label[11]; // space padded, no terminator + + // Added fields: + + uint32_t bytes_per_cluster; + +} Fat16BootSector; + + +/** FAT filesystem handle - private fields! */ +typedef struct __attribute__((packed)) { + // Backing block device + const BLOCKDEV* dev; + + // Root directory sector start + uint32_t rd_addr; + + // Start of first cluster (number "2") + uint32_t data_addr; + + // Start of fat table + uint32_t fat_addr; + + // Boot sector data struct + Fat16BootSector bs; +} FAT16; + + +/** File handle struct */ +typedef struct __attribute__((packed)) { + + // Fields loaded directly from disk: + + uint8_t name[8]; // Starting 0x05 converted to 0xE5, other "magic chars" left intact + uint8_t ext[3]; + uint8_t attribs; // composed of FA_* constants + // 12 bytes skipped + uint16_t clu_start; + uint32_t size; + + // Added fields: + + FAT16_FT type; + + // --- Private fields --- + + // Cursor + uint32_t cur_abs; // absolute position in device + uint32_t cur_rel; // relative position in file + uint16_t cur_clu; // cluster where the cursor is + uint16_t cur_ofs; // offset within the active cluster + + // File position in the directory + uint16_t clu; // first cluster of directory + uint16_t num; // fiel entry number + + // pointer to FAT + const FAT16* fat; +} FAT16_FILE; + + +/** Initialize a filesystem */ +void fat16_init(const BLOCKDEV* dev, FAT16* fat); + +/** + * Open the first file of the root directory. + * The file may be invalid (eg. a volume label, deleted etc), + * or blank (type FT_NONE) if the filesystem is empty. + * + * Either way, the prev and next functions will work as expected. + */ +void fat16_open_root(const FAT16* fat, FAT16_FILE* file); + +/** + * Resolve volume label. + */ +char* fat16_volume_label(const FAT16* fat, char* str); + + +// ----------- FILE I/O ------------- + + +/** + * Move file cursor to a position relative to file start + * Returns false on I/O error (bad file, out of range...) + */ +bool fat16_fseek(FAT16_FILE* file, uint32_t addr); + + +/** + * Read bytes from file into memory + * Returns false on I/O error (bad file, out of range...) + */ +bool fat16_fread(FAT16_FILE* file, void* target, uint32_t len); + + + +// --------- NAVIGATION ------------ + + +/** Go to previous file in the directory (false = no prev file) */ +bool fat16_prev(FAT16_FILE* file); + + +/** Go to next file in directory (false = no next file) */ +bool fat16_next(FAT16_FILE* file); + + +/** Open a directory (file is a directory entry) */ +bool fat16_opendir(FAT16_FILE* file); + + + +// -------- FILE INSPECTION ----------- + +/** Check if file is a valid entry, or long-name/label/deleted */ +bool fat16_is_file_valid(const FAT16_FILE* file); + + +/** Get opened file's type */ +FAT16_FT fat16_get_type(const FAT16_FILE* file); + + +/** + * Resolve a file name, trim spaces and add null terminator. + * Returns the passed char*, or NULL on error. + */ +char* fat16_display_name(const FAT16_FILE* file, char* str); diff --git a/imgs/.gitignore b/imgs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/imgs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/main.c b/main.c new file mode 100644 index 0000000..b86c591 --- /dev/null +++ b/main.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include + +#include "fat16.h" + + + +// ------------- test bed ---------------- + +BLOCKDEV test; +FILE* testf; + +void test_seek(const uint32_t pos) +{ + fseek(testf, pos, SEEK_SET); +} + +void test_rseek(const uint16_t pos) +{ + fseek(testf, pos, SEEK_CUR); +} + +void test_read(void* dest, const uint16_t len) +{ + for (int a = 0; a < len; a++) { + fread(dest+a, 1, 1, testf); + } +} + +void test_aread(void* dest, const uint16_t len, const uint32_t addr) +{ + test_seek(addr); + test_read(dest, len); +} + +void test_write(const void* source, const uint16_t len) +{ + for (int a = 0; a < len; a++) { + fwrite(source+a, 1, 1, testf); + } +} + +void test_awrite(const void* source, const uint16_t len, const uint32_t addr) +{ + test_seek(addr); + test_write(source, len); +} + +void test_open() +{ + test.read = &test_read; + test.aread = &test_aread; + test.write = &test_write; + test.awrite = &test_awrite; + test.seek = &test_seek; + test.rseek = &test_rseek; + + testf = fopen("imgs/dump_sd.img", "rb+"); +} + +void test_close() +{ + fflush(testf); + fclose(testf); +} + + +// --- testing --- + +int main(int argc, char const *argv[]) +{ + uint32_t i32; + uint16_t i16; + uint8_t i8; + + test_open(); + + // Initialize the FS + FAT16 fat; + fat16_init(&test, &fat); + + FAT16_FILE file; + fat16_open_root(&fat, &file); + + char str[12]; + + printf("Disk label: %s\n", fat16_volume_label(&fat, str)); + + do { + if (!fat16_is_file_valid(&file)) continue; + + printf("File name: %s, %c, %d B\n", + fat16_display_name(&file, str), + file.type, file.size); + + } while (fat16_next(&file)); + + test_close(); + + return 0; +}