From 9b61123c20b9d04e892a4e8b08302725df254927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Tue, 9 Jun 2015 01:31:18 +0200 Subject: [PATCH] function to create a directory --- Makefile | 2 +- fat16.c | 346 ++++++++++++++++++++++++++++++++----------------------- fat16.h | 71 ++++++++---- main.c | 59 +++++++--- test | Bin 0 -> 29408 bytes 5 files changed, 297 insertions(+), 181 deletions(-) create mode 100755 test diff --git a/Makefile b/Makefile index f10f027..13096f4 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ all: main main: main.c - gcc -std=gnu99 main.c fat16.c -o test -g + gcc -Wall -std=gnu99 main.c fat16.c -o test -g run: main ./test diff --git a/fat16.c b/fat16.c index 43f0adc..c4fce6e 100644 --- a/fat16.c +++ b/fat16.c @@ -57,16 +57,16 @@ uint16_t read_fat(const FAT16* fat, const uint16_t cluster); /** Find absolute address of first boot sector. Returns 0 on failure. */ uint32_t find_bs(const BLOCKDEV* dev) { - // Reference structure: + // 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; + // 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; @@ -121,7 +121,8 @@ void read_bs(const BLOCKDEV* dev, Fat16BootSector* info, const uint32_t addr) if (info->total_sectors == 0) { dev->load(&(info->total_sectors), 4); - } else + } + else { dev->rseek(4); // tsl } @@ -236,19 +237,18 @@ bool append_cluster(const FAT16* fat, const uint16_t clu) void free_cluster_chain(const FAT16* fat, uint16_t clu) { - do { + do + { // get address of the next cluster const uint16_t clu2 = read_fat(fat, clu); // mark cluster as unused write_fat(fat, clu, 0x0000); - printf("Cluster free at %d\n", clu); - // advance clu = clu2; - - } while (clu != 0xFFFF); + } + while (clu != 0xFFFF); } @@ -258,7 +258,8 @@ void free_cluster_chain(const FAT16* fat, uint16_t clu) */ bool dir_contains_file_raw(FAT16_FILE* dir, char* fname) { - do { + do + { bool diff = false; for (uint8_t i = 0; i < 11; i++) { @@ -271,7 +272,8 @@ bool dir_contains_file_raw(FAT16_FILE* dir, char* fname) if (!diff) return true; - } while (fat16_next(dir)); + } + while (fat16_next(dir)); return false; } @@ -290,7 +292,8 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c if (dir_cluster == 0) { addr = clu_addr(fat, dir_cluster) + num * 32; // root directory, max 512 entries. - } else + } + else { addr = clu_offs(fat, dir_cluster, num * 32); // cluster + N (wrapping to next cluster if needed) } @@ -307,7 +310,7 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c file->type = FT_FILE; - switch(file->name[0]) + switch (file->name[0]) { case 0x00: file->type = FT_NONE; @@ -327,7 +330,8 @@ void open_file(const FAT16* fat, FAT16_FILE* file, const uint16_t dir_cluster, c { // ".." directory file->type = FT_PARENT; - } else + } + else { // "." directory file->type = FT_SELF; @@ -382,41 +386,43 @@ void fat16_init(const BLOCKDEV* dev, FAT16* fat) */ bool fat16_fseek(FAT16_FILE* 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 >= file->fat->bs.bytes_per_cluster) + while (addr >= fat->bs.bytes_per_cluster) { uint32_t next; // Go to next cluster, allocate if needed - do { - next = next_clu(file->fat, file->cur_clu); + do + { + next = next_clu(fat, file->cur_clu); if (next == 0xFFFF) { // reached end of allocated space // add one more cluster - if (!append_cluster(file->fat, file->cur_clu)) + if (!append_cluster(fat, file->cur_clu)) { return false; } - printf("Allocating new cluster due to seek past EOF\n"); } - } while(next == 0xFFFF); - + } + while (next == 0xFFFF); file->cur_clu = next; - addr -= file->fat->bs.bytes_per_cluster; + addr -= fat->bs.bytes_per_cluster; } - file->cur_abs = clu_addr(file->fat, file->cur_clu) + addr; + file->cur_abs = clu_addr(fat, file->cur_clu) + addr; file->cur_ofs = addr; // Physically seek to that location - file->fat->dev->seek(file->cur_abs); + fat->dev->seek(file->cur_abs); return true; } @@ -427,7 +433,8 @@ bool fat16_fseek(FAT16_FILE* file, uint32_t addr) */ bool fat16_is_file_valid(const FAT16_FILE* file) { - switch (file->type) { + switch (file->type) + { case FT_FILE: case FT_SUBDIR: case FT_SELF: @@ -444,18 +451,11 @@ bool fat16_is_file_valid(const FAT16_FILE* file) bool fat16_fread(FAT16_FILE* file, void* target, uint32_t len) { - if (file->cur_abs == 0xFFFF) { - printf("File at 0xFFFF\n"); + if (file->cur_abs == 0xFFFF) return false; // file at the end already - } if (file->cur_rel + len > file->size) - { - // Attempt to read more than what is available - printf("Attempt to read more than what is available\n"); - return false; - } - + return false; // Attempt to read more than what is available const FAT16* fat = file->fat; const BLOCKDEV* dev = fat->dev; @@ -493,7 +493,7 @@ bool fat16_fread(FAT16_FILE* file, void* target, uint32_t len) } -bool fat16_fwrite(FAT16_FILE* file, void* src, uint32_t len) +bool fat16_fwrite(FAT16_FILE* file, void* source, uint32_t len) { const FAT16* fat = file->fat; const BLOCKDEV* dev = fat->dev; @@ -507,8 +507,10 @@ bool fat16_fwrite(FAT16_FILE* file, void* src, uint32_t len) { const uint32_t pos_start = file->cur_rel; - // Seek to the last position, using fseek to allocate clusters - if (!fat16_fseek(file, pos_start + len)) return false; + // Seek to the last position + // -> fseek will allocate clusters + if (!fat16_fseek(file, pos_start + len)) + return false; // error in seek // Write starts beyond EOF - creating a zero-filled "hole" if (file->cur_rel > file->size) @@ -532,8 +534,6 @@ bool fat16_fwrite(FAT16_FILE* file, void* src, uint32_t len) dev->write(0); } - printf("Wrote %d filler zeros.\n", chunk); - // subtract from "needed" what was just placed fill -= chunk; @@ -545,7 +545,7 @@ bool fat16_fwrite(FAT16_FILE* file, void* src, uint32_t len) } // Save new size - fat16_set_file_size(file, pos_start + len); + fat16_resize(file, pos_start + len); // Seek back to where it was before fat16_fseek(file, pos_start); @@ -560,25 +560,25 @@ bool fat16_fwrite(FAT16_FILE* file, void* src, uint32_t len) // store the chunk dev->seek(file->cur_abs); - dev->store(src, chunk); + dev->store(source, chunk); // advance cursors file->cur_abs += chunk; file->cur_rel += chunk; file->cur_ofs += chunk; - src += chunk; // advance the source pointer + // 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; } - printf("Stored %d bytes of data.\n", chunk); - // subtract written length len -= chunk; } @@ -597,20 +597,16 @@ bool fat16_next(FAT16_FILE* file) if (file->clu == 0 && file->num >= fat->bs.root_entries) return false; // attempt to read outside root directory. - uint32_t addr = clu_offs(fat, file->clu, (file->num + 1) * 32); + 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; if zero, can't read (file is NONE) - // FIXME this may be a problem when creating a new file...? - uint8_t x; + // read first byte of the file entry dev->seek(addr); - x = dev->read(); - - if (x == 0) - return false; + if (dev->read() == 0) + return false; // can't read (file is NONE) - open_file(fat, file, file->clu, file->num+1); + open_file(fat, file, file->clu, file->num + 1); return true; } @@ -622,11 +618,7 @@ bool fat16_prev(FAT16_FILE* file) if (file->num == 0) return false; // first file already - open_file(file->fat, file, file->clu, file->num-1); - -/* // Skip bad files - if (!fat16_is_file_valid(file)) - fat16_prev(file);*/ + open_file(file->fat, file, file->clu, file->num - 1); return true; } @@ -639,7 +631,7 @@ void fat16_first(FAT16_FILE* file) } -/** Open a directory */ +/** Open a directory denoted by the file. */ bool fat16_opendir(FAT16_FILE* file) { // Don't open non-dirs and "." directory. @@ -651,7 +643,7 @@ bool fat16_opendir(FAT16_FILE* file) } -void fat16_open_root(const FAT16* fat, FAT16_FILE* file) +void fat16_root(const FAT16* fat, FAT16_FILE* file) { open_file(fat, file, 0, 0); } @@ -662,104 +654,170 @@ void fat16_open_root(const FAT16* fat, FAT16_FILE* file) * If file is found, "dir" will contain it's handle. * Either way, "dir" gets modified and you may need to rewind it afterwards. */ -bool fat16_find_file(FAT16_FILE* dir, const char* name) +bool fat16_find(FAT16_FILE* dir, const char* name) { char fname[11]; - fat16_undisplay_name(name, fname); + fat16_rawname(name, fname); return dir_contains_file_raw(dir, fname); } -bool fat16_newfile(FAT16_FILE* dir, FAT16_FILE* file, const char* name) +/** Go through a directory, and open first NONE or DELETED file. */ +bool find_empty_file_slot(FAT16_FILE* file) { - // Convert filename to zero padded raw string - char fname[11]; - fat16_undisplay_name(name, fname); - - // Abort if file already exists - bool exists = dir_contains_file_raw(dir, fname); - fat16_first(dir); // rewind dir - if (exists) return false; - + const uint16_t clu = file->clu; + const FAT16* fat = file->fat; // Find free directory entry that can be used - uint16_t clu = dir->clu; - const FAT16* fat = dir->fat; - for (uint16_t num = 0; num < 0xFFFF; num++) { // root directory has fewer entries, error if trying // to add one more. - if (dir->clu == 0 && num >= fat->bs.root_entries) + 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, dir->clu, num * 32); + 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, dir->clu)) return false; + if (!append_cluster(fat, file->clu)) return false; } // if new cluster was just added, repeat. - } while (addr == 0xFFFF); - + } + 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) { - const uint16_t newclu = alloc_cluster(fat); - const uint32_t entrystart = clu_offs(fat, clu, num * 32); + return true; + } + } - // store the file name - fat->dev->seek(entrystart); + return false; // not found. +} - // filename, without dor, zero-padded. - fat->dev->store(fname, 11); - fat->dev->write(0); // attributes +/** 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; - // 10 reserved, 2+2 date & time - for (uint8_t i = 0; i < 14; i++) - { - fat->dev->write(0); - } + const uint32_t entrystart = clu_offs(fat, file->clu, file->num * 32); - fat->dev->write16(newclu); // starting cluster + // store the file name + dev->seek(entrystart); + dev->store(fname, 11); - // file size (uint32_t) - fat->dev->write16(0); - fat->dev->write16(0); + // attributes + dev->write(attribs); - // reopen file, load the information just written - open_file(fat, file, clu, num); - return true; - } + // 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); } - return false; + + // 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); } -char* fat16_volume_label(const FAT16* fat, char* str) +bool fat16_mkfile(FAT16_FILE* file, const char* name) +{ + // Convert filename to zero padded raw string + char fname[11]; + fat16_rawname(name, fname); + + // Abort if file already exists + bool exists = dir_contains_file_raw(file, fname); + fat16_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 fat16_mkdir(FAT16_FILE* file, const char* name) +{ + // Convert filename to zero padded raw string + char fname[11]; + fat16_rawname(name, fname); + + // Abort if file already exists + bool exists = dir_contains_file_raw(file, fname); + fat16_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. + fat16_first(file); + + return true; +} + + +char* fat16_disk_label(const FAT16* fat, char* label_out) { FAT16_FILE first; - fat16_open_root(fat, &first); + fat16_root(fat, &first); - if (first.type == FT_LABEL) { - return fat16_display_name(&first, str); + if (first.type == FT_LABEL) + { + return fat16_dispname(&first, label_out); } // find where spaces end - uint8_t j = 10; + int8_t j = 10; for (; j >= 0; j--) { if (fat->bs.volume_label[j] != ' ') break; @@ -769,25 +827,25 @@ char* fat16_volume_label(const FAT16* fat, char* str) uint8_t i; for (i = 0; i <= j; i++) { - str[i] = fat->bs.volume_label[i]; + label_out[i] = fat->bs.volume_label[i]; } - str[i] = 0; // ender + label_out[i] = 0; // ender - return str; + return label_out; } -char* fat16_display_name(const FAT16_FILE* file, char* str) +char* fat16_dispname(const FAT16_FILE* 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) + 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; + int8_t j = 7; for (; j >= 0; j--) { if (file->name[j] != ' ') break; @@ -798,37 +856,37 @@ char* fat16_display_name(const FAT16_FILE* file, char* str) uint8_t i; for (i = 0; i <= j; i++) { - str[i] = file->name[i]; + disp_out[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; + disp_out[i] = 0; // end of string + return disp_out; } // add a dot if (file->type != FT_LABEL) // volume label has no dot! - str[i++] = '.'; + disp_out[i++] = '.'; // Add extension chars for (j = 8; j < 11; j++, i++) { const char c = file->name[j]; if (c == ' ') break; - str[i] = c; + disp_out[i] = c; } - str[i] = 0; // end of string + disp_out[i] = 0; // end of string - return str; + return disp_out; } -char* fat16_undisplay_name(const char* name, char* fixed) +char* fat16_rawname(const char* disp_in, char* raw_out) { uint8_t name_c = 0, wr_c = 0; bool filling = false; @@ -836,9 +894,10 @@ char* fat16_undisplay_name(const char* name, char* fixed) for (; wr_c < 11; wr_c++) { // start filling with spaces if end of filename reached - char c = name[name_c]; + uint8_t c = disp_in[name_c]; // handle special rule for 0xE5 - if (name_c == 0 && c == 0xE5) { + if (name_c == 0 && c == 0xE5) + { c = 0x05; } @@ -851,7 +910,7 @@ char* fat16_undisplay_name(const char* name, char* fixed) if (c == '.') { name_c++; // skip the dot - c = name[name_c]; + c = disp_in[name_c]; at_ext = true; } } @@ -863,13 +922,14 @@ char* fat16_undisplay_name(const char* name, char* fixed) if (!at_ext) { // try to advance past dot (if any) - while(true) + while (true) { - c = name[name_c++]; + c = disp_in[name_c++]; if (c == 0) break; - if (c == '.') { + if (c == '.') + { // read char PAST the dot - c = name[name_c]; + c = disp_in[name_c]; at_ext = true; break; } @@ -887,21 +947,21 @@ char* fat16_undisplay_name(const char* name, char* fixed) if (!filling) { // copy char of filename - fixed[wr_c] = name[name_c++]; + raw_out[wr_c] = disp_in[name_c++]; } else { // add a filler space - fixed[wr_c] = ' '; + raw_out[wr_c] = ' '; } } - return fixed; + return raw_out; } /** Write new file size (also to the disk). Does not allocate clusters. */ -void fat16_set_file_size(FAT16_FILE* file, uint32_t size) +void fat16_resize(FAT16_FILE* file, uint32_t size) { const FAT16* fat = file->fat; const BLOCKDEV* dev = file->fat->dev; @@ -919,8 +979,6 @@ void fat16_set_file_size(FAT16_FILE* file, uint32_t size) const uint16_t next = next_clu(fat, file->cur_clu); if (next != 0xFFFF) { - printf("Trimming file clusters\n"); - free_cluster_chain(fat, next); // Mark that there's no further clusters @@ -929,7 +987,7 @@ void fat16_set_file_size(FAT16_FILE* file, uint32_t size) } -void fat16_delete_file(FAT16_FILE* file) +void fat16_delete(FAT16_FILE* file) { const FAT16* fat = file->fat; diff --git a/fat16.h b/fat16.h index 9fc1f11..4460878 100644 --- a/fat16.h +++ b/fat16.h @@ -1,7 +1,8 @@ #pragma once /** Abstract block device interface */ -typedef struct { +typedef struct +{ // Sequential read void (*load)(void* dest, const uint16_t len); // Sequential write @@ -24,7 +25,8 @@ typedef struct { // ------------------------------- /** file types (values don't matter) */ -typedef enum { +typedef enum +{ FT_NONE = '-', FT_DELETED = 'x', FT_SUBDIR = 'D', @@ -46,7 +48,8 @@ typedef enum { /** Boot Sector structure - INTERNAL! */ -typedef struct __attribute__((packed)) { +typedef struct __attribute__((packed)) +{ // Fields loaded directly from disk: @@ -66,11 +69,13 @@ typedef struct __attribute__((packed)) { uint32_t bytes_per_cluster; -} Fat16BootSector; +} +Fat16BootSector; /** FAT filesystem handle - private fields! */ -typedef struct __attribute__((packed)) { +typedef struct __attribute__((packed)) +{ // Backing block device const BLOCKDEV* dev; @@ -85,12 +90,13 @@ typedef struct __attribute__((packed)) { // Boot sector data struct Fat16BootSector bs; -} FAT16; +} +FAT16; /** File handle struct */ -typedef struct __attribute__((packed)) { - +typedef struct __attribute__((packed)) +{ // Fields loaded directly from disk: uint8_t name[11]; // Starting 0x05 converted to 0xE5, other "magic chars" left intact @@ -117,12 +123,14 @@ typedef struct __attribute__((packed)) { // pointer to FAT const FAT16* fat; -} FAT16_FILE; +} +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), @@ -130,12 +138,13 @@ void fat16_init(const BLOCKDEV* dev, FAT16* fat); * * Either way, the prev and next functions will work as expected. */ -void fat16_open_root(const FAT16* fat, FAT16_FILE* file); +void fat16_root(const FAT16* fat, FAT16_FILE* file); + /** - * Resolve volume label. + * Resolve the disk label. */ -char* fat16_volume_label(const FAT16* fat, char* str); +char* fat16_disk_label(const FAT16* fat, char* label_out); // ----------- FILE I/O ------------- @@ -154,29 +163,39 @@ bool fat16_fseek(FAT16_FILE* file, uint32_t addr); */ bool fat16_fread(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* src, uint32_t len); +bool fat16_fwrite(FAT16_FILE* file, void* source, uint32_t len); + /** * Create a new file in given folder * - * directory ... parent folder's first entry - * file ... where to store info about newly opened file + * file ... open directory; new file is opened into this handle. * name ... name of the new file, including extension */ -bool fat16_newfile(FAT16_FILE* directory, FAT16_FILE* file, const char* name); +bool fat16_mkfile(FAT16_FILE* file, const char* name); + + +/** + * Create a sub-directory of given name. + * Directory is allocated and populated with entries "." and ".." + */ +bool fat16_mkdir(FAT16_FILE* file, const char* name); + /** * Write new file size (also to the disk). * Allocates / frees needed clusters, does not erase them. */ -void fat16_set_file_size(FAT16_FILE* file, uint32_t size); +void fat16_resize(FAT16_FILE* file, uint32_t size); + /** Delete a file entry and free clusters. Does NOT descend into subdirectories. */ -void fat16_delete_file(FAT16_FILE* file); +void fat16_delete(FAT16_FILE* file); // --------- NAVIGATION ------------ @@ -184,21 +203,28 @@ void fat16_delete_file(FAT16_FILE* file); /** 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) */ + +/** + * Open a subdirectory denoted by the file. + * Provided handle changes to first entry of the directory. + */ bool fat16_opendir(FAT16_FILE* file); + /** Rewind to first file in directory */ 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. */ -bool fat16_find_file(FAT16_FILE* dir, const char* name); +bool fat16_find(FAT16_FILE* dir, const char* name); // -------- FILE INSPECTION ----------- @@ -211,11 +237,12 @@ bool fat16_is_file_valid(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); +char* fat16_dispname(const FAT16_FILE* file, char* disp_out); + /** * Convert filename to zero-padded fixed length one * Returns the passed char*. */ -char* fat16_undisplay_name(const char* name, char* fixed); +char* fat16_rawname(const char* disp_in, char* raw_out); diff --git a/main.c b/main.c index 22a62b2..37c4434 100644 --- a/main.c +++ b/main.c @@ -90,12 +90,8 @@ void test_close() // --- testing --- -int main(int argc, char const *argv[]) +int main() { - uint32_t i32; - uint16_t i16; - uint8_t i8; - test_open(); // Initialize the FS @@ -103,22 +99,57 @@ int main(int argc, char const *argv[]) fat16_init(&test, &fat); FAT16_FILE file; - fat16_open_root(&fat, &file); + fat16_root(&fat, &file); char str[12]; - printf("Disk label: %s\n", fat16_volume_label(&fat, str)); + printf("Disk label: %s\n", fat16_disk_label(&fat, str)); - do { + do + { if (!fat16_is_file_valid(&file)) continue; printf("File name: %s, %c, %d B, @ 0x%x\n", - fat16_display_name(&file, str), - file.type, file.size, file.clu_start); - - } while (fat16_next(&file)); - - fat16_open_root(&fat, &file); + fat16_dispname(&file, str), + file.type, file.size, file.clu_start); + + } + while (fat16_next(&file)); + + 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); + } + } + } + + //fat16_mkfile(&file, "banana.exe"); + //fat16_fwrite(&file, "THIS IS A STRING STORED IN A FILE CALLED BANANA!\n", 49); + +// fat16_find(&file, "banana.exe"); +// char m[49]; +// fat16_fread(&file, m, 49); +// printf("%.49s\n", m); + + //fat16_mkdir(&file, "FOLDER2"); // bool found = fat16_find_file(&file, "NEW_FILE.DAT"); // diff --git a/test b/test new file mode 100755 index 0000000000000000000000000000000000000000..3e2a2464d59787c70e79511e9e246124fd4c99ad GIT binary patch literal 29408 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 z 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 z