#include #include #include #include "fat16.h" // ============== INTERNAL PROTOTYPES ================== /** Read boot sector from given address */ void read_bs(const BLOCKDEV* dev, Fat16BootSector* info, const uint32_t addr); /** Find absolute address of first BootSector. Returns 0 on failure. */ uint32_t find_bs(const BLOCKDEV* dev); /** Get cluster's starting address */ uint32_t clu_addr(const FAT16* fat, const uint16_t cluster); /** Find following cluster using FAT for jumps */ uint16_t next_clu(const FAT16* fat, uint16_t cluster); /** Find relative address in a file, using FAT for cluster lookup */ uint32_t clu_offs(const FAT16* fat, uint16_t cluster, uint32_t addr); /** Read a file entry from directory (dir starting cluster, entry number) */ void open_file(const FAT16* fat, FFILE* file, const uint16_t dir_cluster, const uint16_t num); /** Allocate and chain new cluster to a chain starting at given cluster */ bool append_cluster(const FAT16* fat, const uint16_t clu); /** Allocate a new cluster, clean it, and mark with 0xFFFF in FAT */ uint16_t alloc_cluster(const FAT16* fat); /** Zero out entire cluster. */ void wipe_cluster(const FAT16* fat, const uint16_t clu); /** Free cluster chain, starting at given number */ bool free_cluster_chain(const FAT16* fat, uint16_t clu); /** * Check if there is already a file of given RAW name * Raw name - name as found on disk, not "display name". */ bool dir_find_file_raw(FFILE* dir, const char* fname); /** Write a value into FAT */ void write_fat(const FAT16* fat, const uint16_t cluster, const uint16_t value); /** Read a value from FAT */ uint16_t read_fat(const FAT16* fat, const uint16_t cluster); // =========== INTERNAL FUNCTION IMPLEMENTATIONS ========= uint16_t read16(const BLOCKDEV* dev) { uint16_t a; dev->load(&a, 2); return a; } void write16(const BLOCKDEV* dev, const uint16_t val) { dev->store(&val, 2); } /** Find absolute address of first boot sector. Returns 0 on failure. */ uint32_t 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->seek(addr); tmp = dev->read(); // Check if type is valid if (tmp == 4 || tmp == 6 || tmp == 14) { // read MBR address dev->rseek(3);// skip 3 bytes dev->load(&tmp, 4); tmp = tmp << 9; // multiply address by 512 (sector size) // Verify that the boot sector has a valid signature mark dev->seek(tmp + 510); dev->load(&tmp2, 2); if (tmp2 != 0xAA55) { continue; // continue to next entry } // return absolute MBR address return tmp; } } return 0; } /** Read the boot sector */ void read_bs(const BLOCKDEV* dev, Fat16BootSector* info, const uint32_t addr) { dev->seek(addr + 13); // skip 13 dev->load(&(info->sectors_per_cluster), 6); // spc, rs, nf, re info->total_sectors = 0; dev->load(&(info->total_sectors), 2); // short sectors dev->rseek(1); // md dev->load(&(info->fat_size_sectors), 2); dev->rseek(8); // spt, noh, hs // read or skip long sectors field if (info->total_sectors == 0) { dev->load(&(info->total_sectors), 4); } else { dev->rseek(4); // tsl } dev->rseek(7); // dn, ch, bs, vi dev->load(&(info->volume_label), 11); } void write_fat(const FAT16* fat, const uint16_t cluster, const uint16_t value) { fat->dev->seek(fat->fat_addr + (cluster * 2)); write16(fat->dev, value); } uint16_t read_fat(const FAT16* fat, const uint16_t cluster) { fat->dev->seek(fat->fat_addr + (cluster * 2)); return read16(fat->dev); } /** Get cluster starting address */ uint32_t clu_addr(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 next_clu(const FAT16* fat, uint16_t cluster) { return read_fat(fat, cluster); } /** Find file-relative address in fat table */ uint32_t clu_offs(const FAT16* fat, uint16_t cluster, uint32_t addr) { while (addr >= fat->bs.bytes_per_cluster) { cluster = next_clu(fat, cluster); if (cluster == 0xFFFF) return 0xFFFF; // fail addr -= fat->bs.bytes_per_cluster; } return clu_addr(fat, cluster) + addr; } /** * Zero out entire cluster * This is important only for directory clusters, so we can * zero only every first byte of each file entry, to indicate * that it is unused (FT_NONE). */ void wipe_cluster(const FAT16* fat, const uint16_t clu) { uint32_t addr = clu_addr(fat, clu); const BLOCKDEV* dev = fat->dev; dev->seek(addr); for (uint32_t b = 0; b < fat->bs.bytes_per_cluster; b += 32) { dev->write(0); dev->rseek(32); } } /** Allocate a new cluster, clean it, and mark with 0xFFFF in FAT */ uint16_t alloc_cluster(const FAT16* fat) { // find new unclaimed cluster that can be added to the chain. uint16_t i, b; for (i = 2; i < fat->bs.fat_size_sectors * 256; i++) { // read value from FAT b = read_fat(fat, i); if (b == 0) // unused cluster { // Write FFFF to "i", to mark end of file write_fat(fat, i, 0xFFFF); // Wipe the cluster wipe_cluster(fat, i); return i; } } return 0xFFFF;//error code } /** Allocate and chain new cluster to a chain starting at given cluster */ bool append_cluster(const FAT16* fat, const uint16_t clu) { uint16_t clu2 = alloc_cluster(fat); if (clu2 == 0xFFFF) return false; // Write "i" to "clu" write_fat(fat, clu, clu2); return true; } bool free_cluster_chain(const FAT16* fat, uint16_t clu) { if (clu < 2) return false; do { // get address of the next cluster const uint16_t clu2 = read_fat(fat, clu); // mark cluster as unused write_fat(fat, clu, 0x0000); // advance clu = clu2; } while (clu != 0xFFFF); return true; } /** * Check if there is already a file of given RAW name * Raw name - name as found on disk, not "display name". */ bool dir_find_file_raw(FFILE* dir, const char* fname) { // rewind ff_first(dir); do { bool diff = false; for (uint8_t i = 0; i < 11; i++) { if (dir->name[i] != fname[i]) { diff = true; break; } } // found the right file? if (!diff) { return true; // file is already open. } } while (ff_next(dir)); return false; } /** * Read a file entry * * dir_cluster ... directory start cluster * num ... entry number in the directory */ void open_file(const FAT16* fat, FFILE* file, const uint16_t dir_cluster, const uint16_t num) { // Resolve starting address uint32_t addr; if (dir_cluster == 0) { addr = clu_addr(fat, dir_cluster) + num * 32; // root directory, max 512 entries. } else { addr = clu_offs(fat, dir_cluster, num * 32); // cluster + N (wrapping to next cluster if needed) } fat->dev->seek(addr); fat->dev->load(file, 12); // name, ext, attribs fat->dev->rseek(14); // skip 14 bytes fat->dev->load(((void*)file) + 12, 6); // read remaining bytes file->clu = dir_cluster; file->num = num; // add a FAT pointer file->fat = fat; // Resolve filename & type file->type = FT_FILE; const uint8_t c = file->name[0]; switch (c) { 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: if (c < 32) { file->type = FT_INVALID; // File is corrupt, treat it as invalid return; // avoid trying to seek } else { 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 return; // do not seek } else if (file->attribs == 0x0F) { file->type = FT_LFN; // long name special file, can be ignored return; // do not seek } // Init cursors ff_seek(file, 0); } /** * Write information into a file header. * "file" is an open handle. */ void write_file_header(FFILE* file, const char* fname_raw, const uint8_t attribs, const uint16_t clu_start) { const BLOCKDEV* dev = file->fat->dev; const uint32_t entrystart = clu_offs(file->fat, file->clu, file->num * 32); // store the file name dev->seek(entrystart); dev->store(fname_raw, 11); // attributes dev->write(attribs); // 10 reserved, 2+2 date & time // (could just skip, but better to fill with zeros) for (uint8_t i = 0; i < 14; i++) { dev->write(0); } // addr of the first file cluster write16(dev, clu_start); // file size (uint32_t) write16(dev, 0); write16(dev, 0); // reopen file - load & parse the information just written open_file(file->fat, file, file->clu, file->num); } // =============== PUBLIC FUNCTION IMPLEMENTATIONS ================= /** Initialize a FAT16 handle */ bool ff_init(const BLOCKDEV* dev, FAT16* fat) { const uint32_t bs_a = find_bs(dev); if (bs_a == 0) return false; fat->dev = dev; 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); return true; } /** * Move file cursor to a position relative to file start * Allows seek past end of file, will allocate new cluster if needed. */ bool ff_seek(FFILE* file, uint32_t addr) { const FAT16* fat = file->fat; // Store as rel file->cur_rel = addr; // Rewind and resolve abs, clu, ofs file->cur_clu = file->clu_start; while (addr >= fat->bs.bytes_per_cluster) { uint32_t next; // Go to next cluster, allocate if needed do { next = next_clu(fat, file->cur_clu); if (next == 0xFFFF) { // reached end of allocated space // add one more cluster if (!append_cluster(fat, file->cur_clu)) { return false; } } } while (next == 0xFFFF); file->cur_clu = next; addr -= fat->bs.bytes_per_cluster; } file->cur_abs = clu_addr(fat, file->cur_clu) + addr; file->cur_ofs = addr; // Physically seek to that location fat->dev->seek(file->cur_abs); return true; } /** * Check if file is a regular file or directory entry. * Those files can be shown to user. */ bool ff_is_regular(const FFILE* 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)) uint16_t ff_read(FFILE* file, void* target, uint16_t len) { if (file->cur_abs == 0xFFFF) return 0; // file at the end already if (file->cur_rel + len > file->size) { if (file->cur_rel > file->size) return 0; len = file->size - file->cur_rel; //return false; // Attempt to read more than what is available } const uint16_t len_orig = len; const FAT16* fat = file->fat; const BLOCKDEV* dev = fat->dev; while (len > 0 && file->cur_rel < file->size) { // How much can be read from the cluster uint16_t chunk = MIN(file->size - file->cur_rel, MIN(fat->bs.bytes_per_cluster - file->cur_ofs, len)); // read the chunk dev->seek(file->cur_abs); dev->load(target, chunk); // move the cursors file->cur_abs += chunk; file->cur_rel += chunk; file->cur_ofs += chunk; // move target pointer target += chunk; // reached end of cluster? if (file->cur_ofs >= fat->bs.bytes_per_cluster) { file->cur_clu = next_clu(fat, file->cur_clu); file->cur_abs = clu_addr(fat, file->cur_clu); file->cur_ofs = 0; } // subtract read length len -= chunk; } return len_orig; } bool ff_write_str(FFILE* file, const char* source) { uint16_t len = 0; for (; source[len] != 0; len++); return ff_write(file, source, len); } bool ff_write(FFILE* file, const void* source, uint32_t len) { const FAT16* fat = file->fat; const BLOCKDEV* dev = fat->dev; if (file->cur_abs == 0xFFFF) return false; // file past it's end (rare) // Attempt to write past end of file if (file->cur_rel + len >= file->size) { const uint32_t pos_start = file->cur_rel; // Seek to the last position // -> fseek will allocate clusters if (!ff_seek(file, pos_start + len)) return false; // error in seek // Write starts beyond EOF - creating a zero-filled "hole" if (pos_start > file->size + 1) { // Seek to the end of valid data ff_seek(file, file->size); // fill space between EOF and start-of-write with zeros uint32_t fill = pos_start - file->size; // repeat until all "fill" zeros are stored while (fill > 0) { // How much will fit into this cluster const uint16_t chunk = MIN(fat->bs.bytes_per_cluster - file->cur_ofs, fill); // write the zeros dev->seek(file->cur_abs); for (uint16_t i = 0; i < chunk; i++) { dev->write(0); } // subtract from "needed" what was just placed fill -= chunk; // advance cursors to the next cluster file->cur_clu = next_clu(fat, file->cur_clu); file->cur_abs = clu_addr(fat, file->cur_clu); file->cur_ofs = 0; } } // Store new size file->size = pos_start + len; // Seek back to where it was before ff_seek(file, pos_start); } // (end zerofill) // write the data while (len > 0) { dev->seek(file->cur_abs); uint16_t chunk; if (len == 1) { dev->write(*((uint8_t*)source)); file->cur_abs++; file->cur_rel++; file->cur_ofs++; chunk = 1; } else { // How much can be stored in this cluster chunk = MIN(fat->bs.bytes_per_cluster - file->cur_ofs, len); dev->store(source, chunk); // advance cursors file->cur_abs += chunk; file->cur_rel += chunk; file->cur_ofs += chunk; // Pointer arith! source += chunk; // advance the source pointer } // detect cluster overflow if (file->cur_ofs >= fat->bs.bytes_per_cluster) { // advance to following cluster file->cur_clu = next_clu(fat, file->cur_clu); file->cur_abs = clu_addr(fat, file->cur_clu); file->cur_ofs = 0; } // subtract written length len -= chunk; } return true; } /** Open next file in the directory */ bool ff_next(FFILE* file) { const FAT16* fat = file->fat; const BLOCKDEV* dev = fat->dev; if (file->clu == 0 && file->num >= fat->bs.root_entries) return false; // attempt to read outside root directory. const uint32_t addr = clu_offs(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 dev->seek(addr); if (dev->read() == 0) return false; // can't read (file is NONE) open_file(fat, file, file->clu, file->num + 1); return true; } /** Open previous file in the directory */ bool ff_prev(FFILE* file) { if (file->num == 0) return false; // first file already open_file(file->fat, file, file->clu, file->num - 1); return true; } /** Rewind to first file in directory */ void ff_first(FFILE* file) { open_file(file->fat, file, file->clu, 0); } /** Open a directory denoted by the file. */ bool ff_opendir(FFILE* dir) { // Don't open non-dirs and "." directory. if (!(dir->attribs & FA_DIR) || dir->type == FT_SELF) return false; open_file(dir->fat, dir, dir->clu_start, 0); return true; } void ff_root(const FAT16* fat, FFILE* file) { open_file(fat, file, 0, 0); } /** * Find a file with given "display name" in this directory. * If file is found, "dir" will contain it's handle. * Either way, "dir" gets modified and you may need to rewind it afterwards. */ bool ff_open(FFILE* dir, const char* name) { char fname[11]; ff_rawname(name, fname); return dir_find_file_raw(dir, fname); } /** Go through a directory, and "open" first FT_NONE or FT_DELETED file entry. */ bool find_empty_file_slot(FFILE* file) { const uint16_t clu = file->clu; const FAT16* fat = file->fat; // Find free directory entry that can be used for (uint16_t num = 0; num < 0xFFFF; num++) { // root directory has fewer entries, error if trying // to add one more. if (file->clu == 0 && num >= fat->bs.root_entries) return false; // Resolve addres of next file entry uint32_t addr; do { addr = clu_offs(fat, file->clu, num * 32); if (addr == 0xFFFF) { // end of chain of allocated clusters for the directory // append new cluster, return false on failure if (!append_cluster(fat, file->clu)) return false; } // if new cluster was just added, repeat. } while (addr == 0xFFFF); // Open the file entry open_file(fat, file, clu, num); // Check if can be overwritten if (file->type == FT_DELETED || file->type == FT_NONE) { return true; } } return false; // not found. } bool ff_newfile(FFILE* file, const char* name) { // Convert filename to zero padded raw string char fname[11]; ff_rawname(name, fname); // Abort if file already exists bool exists = dir_find_file_raw(file, fname); ff_first(file); // rewind dir if (exists) return false; // file already exists in the dir. if (!find_empty_file_slot(file)) return false; // error finding a slot // Write into the new slot const uint16_t newclu = alloc_cluster(file->fat); write_file_header(file, fname, 0, newclu); return true; } /** * Create a sub-directory of given name. * Directory is allocated and populated with entries "." and ".." */ bool ff_mkdir(FFILE* file, const char* name) { // Convert filename to zero padded raw string char fname[11]; ff_rawname(name, fname); // Abort if file already exists bool exists = dir_find_file_raw(file, fname); ff_first(file); // rewind dir if (exists) return false; // file already exusts in the dir. if (!find_empty_file_slot(file)) return false; // error finding a slot // Write into the new slot const uint16_t newclu = alloc_cluster(file->fat); write_file_header(file, fname, FA_DIR, newclu); const uint32_t parent_clu = file->clu; open_file(file->fat, file, file->clu_start, 0); write_file_header(file, ". ", FA_DIR, newclu); // Advance to next file slot find_empty_file_slot(file); write_file_header(file, ".. ", FA_DIR, parent_clu); // rewind. ff_first(file); return true; } char* ff_disk_label(const FAT16* fat, char* label_out) { FFILE first; ff_root(fat, &first); if (first.type == FT_LABEL) { return ff_dispname(&first, label_out); } // find where spaces end int8_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++) { label_out[i] = fat->bs.volume_label[i]; } label_out[i] = 0; // ender return label_out; } char* ff_dispname(const FFILE* file, char* disp_out) { // 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 int8_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++) { disp_out[i] = file->name[i]; } // directory entry, no extension if (file->type == FT_SUBDIR || file->type == FT_SELF || file->type == FT_PARENT) { disp_out[i] = 0; // end of string return disp_out; } // add a dot if (file->type != FT_LABEL) // volume label has no dot! disp_out[i++] = '.'; // Add extension chars for (j = 8; j < 11; j++, i++) { const char c = file->name[j]; if (c == ' ') break; disp_out[i] = c; } disp_out[i] = 0; // end of string return disp_out; } char* ff_rawname(const char* disp_in, char* raw_out) { uint8_t name_c = 0, wr_c = 0; bool filling = false; bool at_ext = false; for (; wr_c < 11; wr_c++) { // start filling with spaces if end of filename reached uint8_t c = disp_in[name_c]; // handle special rule for 0xE5 if (name_c == 0 && c == 0xE5) { c = 0x05; } if (c == '.' || c == 0) { if (!filling) { filling = true; if (c == '.') { name_c++; // skip the dot c = disp_in[name_c]; at_ext = true; } } } // if at the start of ext if (wr_c == 8) { if (!at_ext) { // try to advance past dot (if any) while (true) { c = disp_in[name_c++]; if (c == 0) break; if (c == '.') { // read char PAST the dot c = disp_in[name_c]; at_ext = true; break; } } } // if c has valid char for extension if (c != 0 && c != '.') { // start copying again. filling = false; } } if (!filling) { // copy char of filename raw_out[wr_c] = disp_in[name_c++]; } else { // add a filler space raw_out[wr_c] = ' '; } } return raw_out; } FSAVEPOS ff_savepos(const FFILE* file) { FSAVEPOS fsp; fsp.clu = file->clu; fsp.num = file->num; fsp.cur_rel = file->cur_rel; return fsp; } void ff_reopen(FFILE* file, const FSAVEPOS* pos) { open_file(file->fat, file, pos->clu, pos->num); ff_seek(file, pos->cur_rel); } void ff_flush_file(FFILE* file) { const FAT16* fat = file->fat; const BLOCKDEV* dev = file->fat->dev; // Store open page dev->flush(); // Store file size // Find address for storing the size const uint32_t addr = clu_offs(fat, file->clu, file->num * 32 + 28); dev->seek(addr); dev->store(&(file->size), 4); // Seek to the end of the file, to make sure clusters are allocated ff_seek(file, file->size - 1); const uint16_t next = next_clu(fat, file->cur_clu); if (next != 0xFFFF) { free_cluster_chain(fat, next); // Mark that there's no further clusters write_fat(fat, file->cur_clu, 0xFFFF); } } /** Low level no-check file delete and free */ void delete_file_do(FFILE* file) { const FAT16* fat = file->fat; // seek to file record fat->dev->seek(clu_offs(fat, file->clu, file->num * 32)); // mark as deleted fat->dev->write(0xE5); // "deleted" mark // Free clusters, if FILE or SUBDIR and valid clu_start if (file->type == FT_FILE || file->type == FT_SUBDIR) { // free allocated clusters free_cluster_chain(fat, file->clu_start); } file->type = FT_DELETED; } /** Delete a simple file */ bool ff_rmfile(FFILE* file) { switch (file->type) { case FT_FILE: case FT_INVALID: case FT_LFN: case FT_LABEL: delete_file_do(file); return true; default: return false; } } /** Delete an empty directory */ bool ff_rmdir(FFILE* file) { if (file->type != FT_SUBDIR) return false; // not a subdirectory entry const FSAVEPOS orig = ff_savepos(file); // Open the subdir if (!ff_opendir(file)) return false; // could not open // Look for valid files and subdirs in the directory uint8_t cnt = 0; // entry counter, for checking "." and ".." do { // Stop on apparent corrupt structure (missing "." or "..") // Can safely delete the folder. if (cnt == 0 && file->type != FT_SELF) break; if (cnt == 1 && file->type != FT_PARENT) break; // Found valid file if (file->type == FT_SUBDIR || file->type == FT_FILE) { // Valid child file was found, aborting. // reopen original file ff_reopen(file, &orig); return false; } if (cnt < 2) cnt++; } while (ff_next(file)); // reopen original file ff_reopen(file, &orig); // and delete as ordinary file delete_file_do(file); return true; } bool ff_delete(FFILE* file) { switch (file->type) { case FT_DELETED: case FT_NONE: return true; // "deleted successfully" case FT_SUBDIR:; // semicolon needed to allow declaration after "case" // store original file location const FSAVEPOS orig = ff_savepos(file); // open the directory (skip "." and "..") open_file(file->fat, file, file->clu_start, 2); // delete all children do { if (!ff_delete(file)) { // failure // reopen original file ff_reopen(file, &orig); return false; } } while (ff_next(file)); // go up and delete the dir ff_reopen(file, &orig); return ff_rmdir(file); default: // try to delete as a regular file return ff_rmfile(file); } } bool ff_parent(FFILE* file) { // open second entry of the directory open_file(file->fat, file, file->clu, 1); const FSAVEPOS orig = ff_savepos(file); // if it's a valid PARENT link, follow it. if (file->type == FT_PARENT) { open_file(file->fat, file, file->clu_start, 0); return true; } else { // in root already? // reopen original file ff_reopen(file, &orig); return false; } }