From ec528daa491e0095a019e1aba16fc8c5752033cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Tue, 9 Jun 2015 14:11:56 +0200 Subject: [PATCH] Recursive delete. --- .gitignore | 2 +- Makefile | 2 +- fat16.c | 272 +++++++++++++++++++++++++++++++++-------------------- fat16.h | 19 +++- main.c | 34 ++++++- test | Bin 30608 -> 0 bytes 6 files changed, 220 insertions(+), 109 deletions(-) delete mode 100755 test diff --git a/.gitignore b/.gitignore index e11677c..10f6209 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ # Debug files *.dSYM/ -./test +test # Code::Blocks garbage *.depend diff --git a/Makefile b/Makefile index 13096f4..bf2d9d0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ all: main main: main.c - gcc -Wall -std=gnu99 main.c fat16.c -o test -g + gcc -g -Wall -std=gnu99 main.c fat16.c -o test -g run: main ./test diff --git a/fat16.c b/fat16.c index a16f005..b3f0b69 100644 --- a/fat16.c +++ b/fat16.c @@ -37,13 +37,13 @@ uint16_t alloc_cluster(const FAT16* fat); void wipe_cluster(const FAT16* fat, const uint16_t clu); /** Free cluster chain, starting at given number */ -void free_cluster_chain(const FAT16* fat, uint16_t clu); +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_contains_file_raw(FAT16_FILE* dir, char* fname); +bool dir_find_file_raw(FAT16_FILE* dir, char* fname); /** Write a value into FAT */ void write_fat(const FAT16* fat, const uint16_t cluster, const uint16_t value); @@ -51,9 +51,6 @@ 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 ========= @@ -252,8 +249,10 @@ bool append_cluster(const FAT16* fat, const uint16_t clu) } -void free_cluster_chain(const FAT16* fat, uint16_t clu) +bool free_cluster_chain(const FAT16* fat, uint16_t clu) { + if (clu < 2) return false; + do { // get address of the next cluster @@ -266,6 +265,8 @@ void free_cluster_chain(const FAT16* fat, uint16_t clu) clu = clu2; } while (clu != 0xFFFF); + + return true; } @@ -273,8 +274,11 @@ void 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_contains_file_raw(FAT16_FILE* dir, char* fname) +bool dir_find_file_raw(FAT16_FILE* dir, char* fname) { + // rewind + fat16_first(dir); + do { bool diff = false; @@ -287,8 +291,11 @@ bool dir_contains_file_raw(FAT16_FILE* dir, char* fname) } } - if (!diff) return true; - + // found the right file? + if (!diff) + { + return true; // file is already open. + } } while (fat16_next(dir)); @@ -323,6 +330,9 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c file->clu = dir_cluster; file->num = num; + // add a FAT pointer + file->fat = fat; + // Resolve filename & type file->type = FT_FILE; @@ -360,6 +370,7 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c if (c < 32) { file->type = FT_INVALID; // File is corrupt, treat it as invalid + return; // avoid trying to seek } else { @@ -375,34 +386,16 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c 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 } - // add a FAT pointer - file->fat = fat; - // Init cursors - fat16_fseek(file, 0); -} - - -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; + fat16_seek(file, 0); } @@ -443,50 +436,6 @@ void write_file_header(FAT16_FILE* file, const char* fname_raw, const uint8_t at } -/** 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 ================= @@ -508,7 +457,7 @@ void fat16_init(const BLOCKDEV* dev, FAT16* fat) * Move file cursor to a position relative to file start * Allows seek past end of file, will allocate new cluster if needed. */ -bool fat16_fseek(FAT16_FILE* file, uint32_t addr) +bool fat16_seek(FAT16_FILE* file, uint32_t addr) { const FAT16* fat = file->fat; @@ -553,9 +502,10 @@ bool fat16_fseek(FAT16_FILE* file, uint32_t addr) /** - * Check if file is a valid entry (to be shown) + * Check if file is a regular file or directory entry. + * Those files can be shown to user. */ -bool fat16_is_file_valid(const FAT16_FILE* file) +bool fat16_is_regular(const FAT16_FILE* file) { switch (file->type) { @@ -573,7 +523,7 @@ bool fat16_is_file_valid(const FAT16_FILE* file) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) -bool fat16_fread(FAT16_FILE* file, void* target, uint32_t len) +bool fat16_read(FAT16_FILE* file, void* target, uint32_t len) { if (file->cur_abs == 0xFFFF) return false; // file at the end already @@ -617,7 +567,7 @@ bool fat16_fread(FAT16_FILE* file, void* target, uint32_t len) } -bool fat16_fwrite(FAT16_FILE* file, void* source, uint32_t len) +bool fat16_write(FAT16_FILE* file, void* source, uint32_t len) { const FAT16* fat = file->fat; const BLOCKDEV* dev = fat->dev; @@ -633,14 +583,14 @@ bool fat16_fwrite(FAT16_FILE* file, void* source, uint32_t len) // Seek to the last position // -> fseek will allocate clusters - if (!fat16_fseek(file, pos_start + len)) + if (!fat16_seek(file, pos_start + len)) return false; // error in seek // Write starts beyond EOF - creating a zero-filled "hole" if (file->cur_rel > file->size) { // Seek to the end of valid data - fat16_fseek(file, file->size); + fat16_seek(file, file->size); // fill space between EOF and start-of-write with zeros uint32_t fill = pos_start - file->size; @@ -672,7 +622,7 @@ bool fat16_fwrite(FAT16_FILE* file, void* source, uint32_t len) fat16_resize(file, pos_start + len); // Seek back to where it was before - fat16_fseek(file, pos_start); + fat16_seek(file, pos_start); } // (end zerofill) @@ -782,9 +732,56 @@ bool fat16_find(FAT16_FILE* dir, const char* name) { char fname[11]; fat16_rawname(name, fname); - return dir_contains_file_raw(dir, 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(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. +} + + + bool fat16_mkfile(FAT16_FILE* file, const char* name) { // Convert filename to zero padded raw string @@ -792,7 +789,7 @@ bool fat16_mkfile(FAT16_FILE* file, const char* name) fat16_rawname(name, fname); // Abort if file already exists - bool exists = dir_contains_file_raw(file, fname); + bool exists = dir_find_file_raw(file, fname); fat16_first(file); // rewind dir if (exists) return false; // file already exists in the dir. @@ -820,7 +817,7 @@ bool fat16_mkdir(FAT16_FILE* file, const char* name) fat16_rawname(name, fname); // Abort if file already exists - bool exists = dir_contains_file_raw(file, fname); + bool exists = dir_find_file_raw(file, fname); fat16_first(file); // rewind dir if (exists) return false; // file already exusts in the dir. @@ -1018,7 +1015,7 @@ void fat16_resize(FAT16_FILE* file, uint32_t size) dev->store(&size, 4); // Seek to the end of the file, to make sure clusters are allocated - fat16_fseek(file, size - 1); + fat16_seek(file, size - 1); const uint16_t next = next_clu(fat, file->cur_clu); if (next != 0xFFFF) @@ -1030,14 +1027,44 @@ void fat16_resize(FAT16_FILE* file, uint32_t size) } } +/** Low level no-check file delete and free */ +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 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 fat16_rmfile(FAT16_FILE* file) { - if (file->type != FT_FILE) - return false; // not a simple 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_file_do(file); - return true; } @@ -1049,14 +1076,16 @@ bool fat16_rmdir(FAT16_FILE* file) const FAT16* fat = file->fat; - const uint16_t dir_clu = file->clu; - const uint16_t dir_num = file->num; + const uint16_t clu1 = file->clu; + const uint16_t num1 = file->num; - // Look for valid files and subdirs in the directory + + // Open the subdir if (!fat16_opendir(file)) return false; // could not open - uint8_t cnt = 0; + // 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 "..") @@ -1069,7 +1098,7 @@ bool fat16_rmdir(FAT16_FILE* file) { // Valid child file was found, aborting. // reopen original file - open_file(fat, file, dir_clu, dir_num); + open_file(fat, file, clu1, num1); return false; } @@ -1078,7 +1107,7 @@ bool fat16_rmdir(FAT16_FILE* file) while (fat16_next(file)); // reopen original file - open_file(fat, file, dir_clu, dir_num); + open_file(fat, file, clu1, num1); // and delete as ordinary file delete_file_do(file); @@ -1087,10 +1116,52 @@ bool fat16_rmdir(FAT16_FILE* file) } +bool fat16_delete(FAT16_FILE* file) +{ + switch (file->type) + { + case FT_DELETED: + case FT_NONE: + printf("NONE OR DEL\n"); + return true; // "deleted successfully" + + case FT_SUBDIR:; // semicolon needed to allow declaration after "case" + + // store original file location + const uint16_t clu1 = file->clu; + const uint16_t num1 = file->num; + + // open the directory (skip "." and "..") + open_file(file->fat, file, file->clu_start, 2); + + // delete all children + do + { + if (!fat16_delete(file)) + { + // failure + // reopen original file + open_file(file->fat, file, clu1, num1); + return false; + } + } + while (fat16_next(file)); + + // go up and delete the dir + open_file(file->fat, file, clu1, num1); + return fat16_rmdir(file); + + default: + // try to delete as a regular file + return fat16_rmfile(file); + } +} + + bool fat16_parent(FAT16_FILE* file) { - const uint16_t clu = file->clu; - const uint16_t num = file->num; + const uint16_t clu1 = file->clu; + const uint16_t num1 = file->num; // open second entry of the directory open_file(file->fat, file, file->clu, 1); @@ -1103,10 +1174,9 @@ bool fat16_parent(FAT16_FILE* file) } else { - // maybe in root already? - + // in root already? // reopen original file - open_file(file->fat, file, clu, num); + open_file(file->fat, file, clu1, num1); return false; } } diff --git a/fat16.h b/fat16.h index ff34d92..40809a4 100644 --- a/fat16.h +++ b/fat16.h @@ -151,21 +151,21 @@ char* fat16_disk_label(const FAT16* fat, char* label_out); * 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); +bool fat16_seek(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); +bool fat16_read(FAT16_FILE* file, void* target, uint32_t len); /** * Write into file at a "seek" position. * "seek" cursor must be within (0..filesize) */ -bool fat16_fwrite(FAT16_FILE* file, void* source, uint32_t len); +bool fat16_write(FAT16_FILE* file, void* source, uint32_t len); /** @@ -204,6 +204,15 @@ bool fat16_rmfile(FAT16_FILE* file); */ bool fat16_rmdir(FAT16_FILE* file); + +/** + * Delete a file or directory, regardless of type. + * Directories are deleted recursively. + */ +bool fat16_delete(FAT16_FILE* file); + + + // --------- NAVIGATION ------------ @@ -234,7 +243,7 @@ void fat16_first(FAT16_FILE* file); /** * 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. + * If file is NOT found, the handle points to the last entry of the directory. */ bool fat16_find(FAT16_FILE* dir, const char* name); @@ -242,7 +251,7 @@ bool fat16_find(FAT16_FILE* dir, const char* name); // -------- FILE INSPECTION ----------- /** Check if file is a valid entry, or long-name/label/deleted */ -bool fat16_is_file_valid(const FAT16_FILE* file); +bool fat16_is_regular(const FAT16_FILE* file); /** diff --git a/main.c b/main.c index 6b52252..fa9e4ef 100644 --- a/main.c +++ b/main.c @@ -92,7 +92,8 @@ int main() do { - if (!fat16_is_file_valid(&file)) continue; + if (!fat16_is_regular(&file)) + continue; printf("File name: %s, %c, %d B, @ 0x%x\n", fat16_dispname(&file, str), @@ -101,6 +102,37 @@ int main() } while (fat16_next(&file)); + fat16_root(&fat, &file); + +// if (fat16_mkfile(&file, "slepice8.ini")) +// { +// printf("created.\n"); +// char* moo = "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of 'de Finibus Bonorum et Malorum' (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, 'Lorem ipsum dolor sit amet..', comes from a line in section 1.10.32."; + +// fat16_write(&file, moo, strlen(moo)); +// } + + if (fat16_find(&file, "slepice2.ini")) + { + printf("Found.\n"); + + char m[1000000]; + fat16_read(&file, m, file.size); + m[file.size] = 0; + printf("%s\n", m); + +// char* moo = "\n\nContrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of 'de Finibus Bonorum et Malorum' (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, 'Lorem ipsum dolor sit amet..', comes from a line in section 1.10.32. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of 'de Finibus Bonorum et Malorum' (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, 'Lorem ipsum dolor sit amet..', comes from a line in section 1.10.32.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of 'de Finibus Bonorum et Malorum' (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, 'Lorem ipsum dolor sit amet..', comes from a line in section 1.10.32.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of 'de Finibus Bonorum et Malorum' (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, 'Lorem ipsum dolor sit amet..', comes from a line in section 1.10.32.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of 'de Finibus Bonorum et Malorum' (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, 'Lorem ipsum dolor sit amet..', comes from a line in section 1.10.32."; + +// fat16_seek(&file, file.size); +// fat16_write(&file, moo, strlen(moo)); + } + +// if (fat16_find(&file, "banana2.exe")) +// { +// printf("Found.\n"); +// printf("Deleted? %d\n", fat16_delete(&file)); +// } + test_close(); return 0; diff --git a/test b/test deleted file mode 100755 index 8484a1bed6e76028f05f8302ee3a8129eeb3efdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30608 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