FAT16 library written for an ATmega project with SD card
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
c-fat16/fat16.c

1253 lines
25 KiB

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#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.
*/
bool ff_find(FFILE* file, const char* name)
{
// save orig pos
FSAVEPOS orig = ff_savepos(file);
char fname[11];
ff_rawname(name, fname);
bool ret = dir_find_file_raw(file, fname);
if (!ret)
ff_reopen(file, &orig);
return ret;
}
/** 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)
{
const FSAVEPOS orig = ff_savepos(file);
// 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)
{
ff_reopen(file, &orig);
return false; // file already exists in the dir.
}
if (!find_empty_file_slot(file))
{
ff_reopen(file, &orig);
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)
{
const FSAVEPOS orig = ff_savepos(file);
// 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)
{
ff_reopen(file, &orig);
return false; // file already exusts in the dir.
}
if (!find_empty_file_slot(file))
{
ff_reopen(file, &orig);
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;
}
}