From 228099e1d40220095b51abede8506199476b5123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Tue, 9 Jun 2015 02:23:18 +0200 Subject: [PATCH] fat16_parent, func to rm directory --- .gitignore | 2 + fat16.c | 276 +++++++++++++++++++++++++++++++++++------------------ fat16.h | 20 +++- main.c | 63 +++++++----- test | Bin 29408 -> 0 bytes 5 files changed, 244 insertions(+), 117 deletions(-) delete mode 100755 test diff --git a/.gitignore b/.gitignore index 3f1ff6e..e11677c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ # Debug files *.dSYM/ +./test + # Code::Blocks garbage *.depend *.layout diff --git a/fat16.c b/fat16.c index c4fce6e..757f0bb 100644 --- a/fat16.c +++ b/fat16.c @@ -51,6 +51,9 @@ 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); +/** Go through a directory, and open first NONE or DELETED file. */ +bool find_empty_file_slot(FAT16_FILE* file); + // =========== INTERNAL FUNCTION IMPLEMENTATIONS ========= @@ -310,7 +313,8 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c file->type = FT_FILE; - switch (file->name[0]) + const uint8_t c = file->name[0]; + switch (c) { case 0x00: file->type = FT_NONE; @@ -339,7 +343,14 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c break; default: - file->type = FT_FILE; + if (c < 32) + { + file->type = FT_INVALID; // File is corrupt, treat it as invalid + } + else + { + file->type = FT_FILE; + } } // handle subdir, label @@ -353,7 +364,7 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c } else if (file->attribs == 0x0F) { - file->type = FT_LFN; // long name special file, can be safely ignored + file->type = FT_LFN; // long name special file, can be ignored } // add a FAT pointer @@ -364,6 +375,105 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c } +void delete_file_do(FAT16_FILE* 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 allocated clusters + free_cluster_chain(fat, file->clu_start); + + file->type = FT_DELETED; +} + + + +/** + * Write information into a file header. + * "file" is an open handle. + */ +void write_file_header(FAT16_FILE* 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 + dev->write16(clu_start); + + // file size (uint32_t) + dev->write16(0); + dev->write16(0); + + // reopen file - load & parse the information just written + open_file(file->fat, file, file->clu, file->num); +} + + +/** Go through a directory, and "open" first FT_NONE or FT_DELETED file entry. */ +bool find_empty_file_slot(FAT16_FILE* 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. +} + + // =============== PUBLIC FUNCTION IMPLEMENTATIONS ================= /** Initialize a FAT16 handle */ @@ -661,86 +771,6 @@ bool fat16_find(FAT16_FILE* dir, const char* name) return dir_contains_file_raw(dir, fname); } - -/** Go through a directory, and open first NONE or DELETED file. */ -bool find_empty_file_slot(FAT16_FILE* 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. -} - - -/** Write information into a file header (alloc first cluster). "file" is an open handle. */ -void write_file_header(FAT16_FILE* file, const char* fname, const uint8_t attribs, const uint16_t newclu) -{ - const FAT16* fat = file->fat; - const BLOCKDEV* dev = fat->dev; - - const uint32_t entrystart = clu_offs(fat, file->clu, file->num * 32); - - // store the file name - dev->seek(entrystart); - dev->store(fname, 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 - dev->write16(newclu); - - // file size (uint32_t) - dev->write16(0); - dev->write16(0); - - // reopen file - load & parse the information just written - open_file(fat, file, file->clu, file->num); -} - - bool fat16_mkfile(FAT16_FILE* file, const char* name) { // Convert filename to zero padded raw string @@ -986,19 +1016,83 @@ void fat16_resize(FAT16_FILE* file, uint32_t size) } } +/** Delete a simple file */ +bool fat16_rmfile(FAT16_FILE* file) +{ + if (file->type != FT_FILE) + return false; // not a simple file -void fat16_delete(FAT16_FILE* file) + delete_file_do(file); + return true; +} + + +/** Delete an empty directory */ +bool fat16_rmdir(FAT16_FILE* file) { + if (file->type != FT_SUBDIR) + return false; // not a subdirectory entry + const FAT16* fat = file->fat; - // seek to file record - fat->dev->seek(clu_offs(fat, file->clu, file->num * 32)); + const uint16_t dir_clu = file->clu; + const uint16_t dir_num = file->num; - // mark as deleted - fat->dev->write(0xE5); // "deleted" mark + // Look for valid files and subdirs in the directory + if (!fat16_opendir(file)) + return false; // could not open - // free allocated clusters - free_cluster_chain(fat, file->clu_start); + uint8_t cnt = 0; + 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; - file->type = FT_DELETED; + // Found valid file + if (file->type == FT_SUBDIR || file->type == FT_FILE) + { + // Valid child file was found, aborting. + // reopen original file + open_file(fat, file, dir_clu, dir_num); + return false; + } + + if (cnt < 2) cnt++; + } + while (fat16_next(file)); + + // reopen original file + open_file(fat, file, dir_clu, dir_num); + + // and delete as ordinary file + delete_file_do(file); + + return true; +} + + +bool fat16_parent(FAT16_FILE* file) +{ + const uint16_t clu = file->clu; + const uint16_t num = file->num; + + // open second entry of the directory + open_file(file->fat, file, file->clu, 1); + + // 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 + { + // maybe in root already? + + // reopen original file + open_file(file->fat, file, clu, num); + return false; + } } diff --git a/fat16.h b/fat16.h index 4460878..e11dbee 100644 --- a/fat16.h +++ b/fat16.h @@ -33,6 +33,7 @@ typedef enum FT_PARENT = 'P', FT_LABEL = 'L', FT_LFN = '~', + FT_INVALID = 'i', // not recognized weird file FT_SELF = '.', FT_FILE = 'F' } FAT16_FT; @@ -194,8 +195,18 @@ bool fat16_mkdir(FAT16_FILE* file, const char* name); void fat16_resize(FAT16_FILE* file, uint32_t size); -/** Delete a file entry and free clusters. Does NOT descend into subdirectories. */ -void fat16_delete(FAT16_FILE* file); +/** + * Delete a file entry and free clusters. + * Does NOT work on folders. + */ +bool fat16_rmfile(FAT16_FILE* file); + + +/** + * Delete an empty directory. + * Calling with non-subfolder entry or non-empty directory will cause error. + */ +bool fat16_rmdir(FAT16_FILE* file); // --------- NAVIGATION ------------ @@ -214,6 +225,11 @@ bool fat16_next(FAT16_FILE* file); */ bool fat16_opendir(FAT16_FILE* file); +/** + * Open a parent directory. Fails in root. + */ +bool fat16_parent(FAT16_FILE* file); + /** Rewind to first file in directory */ void fat16_first(FAT16_FILE* file); diff --git a/main.c b/main.c index 37c4434..58afa67 100644 --- a/main.c +++ b/main.c @@ -118,31 +118,46 @@ int main() fat16_root(&fat, &file); - if (fat16_find(&file, "FOLDER")) - { - if (fat16_opendir(&file)) - { - printf("Listing FOLDER:\n"); - - do - { - printf("File name: %s, %c, %d B, @ 0x%x\n", - fat16_dispname(&file, str), - file.type, file.size, file.clu_start); - } - while (fat16_next(&file)); - - if (fat16_find(&file, "banana.exe")) - { - char m[49]; - fat16_fread(&file, m, 49); - printf("%.49s\n", m); - } - } - } +// if (fat16_find(&file, "FOLDER")) +// { +// if (fat16_opendir(&file)) +// { +// printf("Listing FOLDER:\n"); + +// do +// { +// printf("File name: %s, %c, %d B, @ 0x%x\n", +// fat16_dispname(&file, str), +// file.type, file.size, file.clu_start); +// } +// while (fat16_next(&file)); + +// char m[49]; + +// if (fat16_find(&file, "banana.exe")) +// { +// fat16_fread(&file, m, 49); +// printf("%.49s\n", m); +// } + +// fat16_rmfile(&file); + +// if (fat16_parent(&file)) { +// printf("Current file: %s\n", fat16_dispname(&file, m)); +// } + +// printf("Found? %d\n", fat16_find(&file, "FOLDER2")); +// printf("Deleted? %d\n", fat16_rmdir(&file)); +// } +// } - //fat16_mkfile(&file, "banana.exe"); - //fat16_fwrite(&file, "THIS IS A STRING STORED IN A FILE CALLED BANANA!\n", 49); +// printf("Found? %d\n", fat16_find(&file, "FOLDER2")); +// printf("Deleted? %d\n", fat16_rmdir(&file)); + +// if (fat16_mkfile(&file, "banana2.exe")) +// { +// fat16_fwrite(&file, "THIS IS A STRING STORED IN A FILE CALLED BANANA!\n", 49); +// } // fat16_find(&file, "banana.exe"); // char m[49]; diff --git a/test b/test deleted file mode 100755 index 3e2a2464d59787c70e79511e9e246124fd4c99ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29408 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 z 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 z