source import

master
Ondřej Hruška 9 years ago
parent 9e251f7300
commit 6696ab7fd9
  1. 3
      .gitignore
  2. 7
      Makefile
  3. 462
      fat16.c
  4. 186
      fat16.h
  5. 2
      imgs/.gitignore
  6. 103
      main.c

3
.gitignore vendored

@ -30,3 +30,6 @@
# Debug files # Debug files
*.dSYM/ *.dSYM/
test

@ -0,0 +1,7 @@
all: main
main: main.c
gcc -std=gnu99 main.c fat16.c -o test -g
run: main
./test

@ -0,0 +1,462 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#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);
}

@ -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);

2
imgs/.gitignore vendored

@ -0,0 +1,2 @@
*
!.gitignore

103
main.c

@ -0,0 +1,103 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#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;
}
Loading…
Cancel
Save