FAT16: Fix incorrect loop condition for root dir
[mfat.git] / mfat.c
blob14fbec3e97cd1c1a8a51957feaaead9a4ef68b87
1 //--------------------------------------------------------------------------------------------------
2 // Copyright (C) 2022 Marcus Geelnard
3 //
4 // Redistribution and use in source and binary forms, with or without modification, are permitted
5 // provided that the following conditions are met:
6 //
7 // 1. Redistributions of source code must retain the above copyright notice, this list of
8 // conditions and the following disclaimer.
9 //
10 // 2. Redistributions in binary form must reproduce the above copyright notice, this list of
11 // conditions and the following disclaimer in the documentation and/or other materials provided
12 // with the distribution.
14 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
15 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
17 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
21 // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22 //--------------------------------------------------------------------------------------------------
24 #include "mfat.h"
26 #include <inttypes.h>
27 #include <string.h> // memcpy, memset
29 //--------------------------------------------------------------------------------------------------
30 // Configuration.
31 //--------------------------------------------------------------------------------------------------
33 // Enable debugging?
34 #ifndef MFAT_ENABLE_DEBUG
35 #define MFAT_ENABLE_DEBUG 0
36 #endif
38 // Enable writing?
39 #ifndef MFAT_ENABLE_WRITE
40 #define MFAT_ENABLE_WRITE 1
41 #endif
43 // Enable MBR support?
44 #ifndef MFAT_ENABLE_MBR
45 #define MFAT_ENABLE_MBR 1
46 #endif
48 // Enable GPT support?
49 #ifndef MFAT_ENABLE_GPT
50 #define MFAT_ENABLE_GPT 1
51 #endif
53 // Number of cached blocks.
54 #ifndef MFAT_NUM_CACHED_BLOCKS
55 #define MFAT_NUM_CACHED_BLOCKS 2
56 #endif
58 // Maximum number of open file descriptors.
59 #ifndef MFAT_NUM_FDS
60 #define MFAT_NUM_FDS 4
61 #endif
63 // Maximum number of partitions to support.
64 #ifndef MFAT_NUM_PARTITIONS
65 #define MFAT_NUM_PARTITIONS 4
66 #endif
68 //--------------------------------------------------------------------------------------------------
69 // Debugging macros.
70 //--------------------------------------------------------------------------------------------------
72 #if MFAT_ENABLE_DEBUG
73 #include <stdio.h>
74 #define DBG(_str) (void)fprintf(stderr, "[MFAT] " _str "\n")
75 #define DBGF(_fmt, ...) (void)fprintf(stderr, "[MFAT] " _fmt "\n", __VA_ARGS__)
76 #else
77 #define DBG(_str)
78 #define DBGF(_fmt, ...)
79 #endif // MFAT_ENABLE_DEBUG
81 //--------------------------------------------------------------------------------------------------
82 // FAT definitions (used for encoding/decoding).
83 //--------------------------------------------------------------------------------------------------
85 // Known partition ID:s.
86 #define MFAT_PART_ID_NONE 0x00
87 #define MFAT_PART_ID_FAT16_LT32GB 0x04
88 #define MFAT_PART_ID_FAT16_GT32GB 0x06
89 #define MFAT_PART_ID_FAT32 0x0b
90 #define MFAT_PART_ID_FAT32_LBA 0x0c
91 #define MFAT_PART_ID_FAT16_GT32GB_LBA 0x0e
93 // File attribute flags.
94 #define MFAT_ATTR_READ_ONLY 0x01
95 #define MFAT_ATTR_HIDDEN 0x02
96 #define MFAT_ATTR_SYSTEM 0x04
97 #define MFAT_ATTR_VOLUME_ID 0x08
98 #define MFAT_ATTR_DIRECTORY 0x10
99 #define MFAT_ATTR_ARCHIVE 0x20
100 #define MFAT_ATTR_LONG_NAME 0x0f
102 //--------------------------------------------------------------------------------------------------
103 // Private types and constants.
104 //--------------------------------------------------------------------------------------------------
106 // A custom boolean type (stdbool is byte-sized which is unnecessarily performance costly).
107 typedef int mfat_bool_t;
108 #define false 0
109 #define true 1
111 // mfat_cached_block_t::state
112 #define MFAT_INVALID 0
113 #define MFAT_VALID 1
114 #define MFAT_DIRTY 2
116 // Different types of caches (we keep independent block types in different caches).
117 #define MFAT_CACHE_DATA 0
118 #define MFAT_CACHE_FAT 1
119 #define MFAT_NUM_CACHES 2
121 // mfat_partition_t::type
122 #define MFAT_TYPE_UNKNOWN 0
123 #define MFAT_TYPE_FAT_UNDECIDED 1 // Indicates a FAT partion of yet unknown type (FAT16 or FAT32).
124 #define MFAT_TYPE_FAT16 2
125 #define MFAT_TYPE_FAT32 3
127 // A collection of variables for keeping track of the current cluster & block position, e.g. during
128 // read/write operations.
129 typedef struct {
130 uint32_t cluster_no; ///< The cluster number.
131 uint32_t block_in_cluster; ///< The block offset within the cluster (0..blocks_per_cluster-1).
132 uint32_t cluster_start_blk; ///< Absolute block number of the first block of the cluster.
133 } mfat_cluster_pos_t;
135 typedef struct {
136 uint32_t type;
137 uint32_t first_block;
138 uint32_t num_blocks;
139 uint32_t blocks_per_cluster;
140 #if MFAT_ENABLE_WRITE
141 uint32_t num_clusters;
142 #endif
143 uint32_t blocks_per_fat;
144 uint32_t num_fats;
145 uint32_t num_reserved_blocks;
146 uint32_t root_dir_block; // Used for FAT16.
147 uint32_t blocks_in_root_dir; // Used for FAT16 (zero for FAT32).
148 uint32_t root_dir_cluster; // Used for FAT32.
149 uint32_t first_data_block;
150 mfat_bool_t boot;
151 } mfat_partition_t;
153 // Static information about a file, as specified in the file system.
154 typedef struct {
155 int part_no; // Partition that this file is located on.
156 uint32_t size; // Total size of file, in bytes.
157 uint32_t first_cluster; // Starting cluster for the file.
158 uint32_t dir_entry_block; // Block number for the directory entry of this file.
159 uint32_t dir_entry_offset; // Offset (in bytes) into the directory entry block.
160 } mfat_file_info_t;
162 typedef struct {
163 mfat_bool_t open; // Is the file open?
164 int oflag; // Flags used when opening the file.
165 uint32_t offset; // Current byte offset relative to the file start (seek offset).
166 uint32_t current_cluster; // Current cluster (representing the current seek offset)
167 mfat_file_info_t info;
168 } mfat_file_t;
170 typedef struct {
171 int state;
172 uint32_t blk_no;
173 uint8_t buf[MFAT_BLOCK_SIZE];
174 } mfat_cached_block_t;
176 typedef struct {
177 mfat_cached_block_t block[MFAT_NUM_CACHED_BLOCKS];
178 #if MFAT_NUM_CACHED_BLOCKS > 1
179 // This is a priority queue: The last item in the queue is an index to the
180 // least recently used cached block item.
181 int pri[MFAT_NUM_CACHED_BLOCKS];
182 #endif
183 } mfat_cache_t;
185 typedef struct {
186 mfat_bool_t initialized;
187 int active_partition;
188 mfat_read_block_fun_t read;
189 #if MFAT_ENABLE_WRITE
190 mfat_write_block_fun_t write;
191 #endif
192 void* custom;
193 mfat_partition_t partition[MFAT_NUM_PARTITIONS];
194 mfat_file_t file[MFAT_NUM_FDS];
195 mfat_cache_t cache[MFAT_NUM_CACHES];
196 } mfat_ctx_t;
198 // Statically allocated state.
199 static mfat_ctx_t s_ctx;
201 //--------------------------------------------------------------------------------------------------
202 // Private functions.
203 //--------------------------------------------------------------------------------------------------
205 static inline uint32_t _mfat_min(uint32_t a, uint32_t b) {
206 return (a < b) ? a : b;
209 static uint32_t _mfat_get_word(const uint8_t* buf) {
210 return ((uint32_t)buf[0]) | (((uint32_t)buf[1]) << 8);
213 static uint32_t _mfat_get_dword(const uint8_t* buf) {
214 return ((uint32_t)buf[0]) | (((uint32_t)buf[1]) << 8) | (((uint32_t)buf[2]) << 16) |
215 (((uint32_t)buf[3]) << 24);
218 static mfat_bool_t _mfat_cmpbuf(const uint8_t* a, const uint8_t* b, const uint32_t nbyte) {
219 for (uint32_t i = 0; i < nbyte; ++i) {
220 if (a[i] != b[i]) {
221 return false;
224 return true;
227 #if MFAT_ENABLE_MBR
228 static mfat_bool_t _mfat_is_fat_part_id(uint32_t id) {
229 switch (id) {
230 case MFAT_PART_ID_FAT16_LT32GB:
231 case MFAT_PART_ID_FAT16_GT32GB:
232 case MFAT_PART_ID_FAT16_GT32GB_LBA:
233 case MFAT_PART_ID_FAT32:
234 case MFAT_PART_ID_FAT32_LBA:
235 return true;
236 default:
237 return false;
240 #endif // MFAT_ENABLE_MBR
242 #if MFAT_ENABLE_GPT
243 static mfat_bool_t _mfat_is_fat_part_guid(const uint8_t* guid) {
244 // Windows Basic data partition GUID = EBD0A0A2-B9E5-4433-87C0-68B6B72699C7
245 static const uint8_t BDP_GUID[] = {0xa2,
246 0xa0,
247 0xd0,
248 0xeb,
249 0xe5,
250 0xb9,
251 0x33,
252 0x44,
253 0x87,
254 0xc0,
255 0x68,
256 0xb6,
257 0xb7,
258 0x26,
259 0x99,
260 0xc7};
261 if (_mfat_cmpbuf(guid, &BDP_GUID[0], sizeof(BDP_GUID))) {
262 // TODO(m): These may also be NTFS partitions. How to detect?
263 return true;
265 return false;
267 #endif // MFAT_ENABLE_GPT
269 static mfat_bool_t _mfat_is_valid_bpb(const uint8_t* bpb_buf) {
270 // Check that the BPB signature is there.
271 if (bpb_buf[510] != 0x55U || bpb_buf[511] != 0xaaU) {
272 DBGF("\t\tInvalid BPB signature: [%02x,%02x]", bpb_buf[510], bpb_buf[511]);
273 return false;
276 // Check for a valid jump instruction (first three bytes).
277 if (bpb_buf[0] != 0xe9 && !(bpb_buf[0] == 0xeb && bpb_buf[2] == 0x90)) {
278 DBGF("\t\tInvalid BPB jump code: [%02x,%02x,%02x]", bpb_buf[0], bpb_buf[1], bpb_buf[2]);
279 return false;
282 // Check for valid bytes per sector.
283 uint32_t bps = _mfat_get_word(&bpb_buf[11]);
284 if (bps != 512 && bps != 1024 && bps != 2048 && bps != 4096) {
285 DBGF("\t\tInvalid BPB bytes per sector: %" PRIu32, bps);
286 return false;
289 return true;
292 static mfat_cached_block_t* _mfat_get_cached_block(uint32_t blk_no, int cache_type) {
293 // Pick the relevant cache.
294 mfat_cache_t* cache = &s_ctx.cache[cache_type];
296 #if MFAT_NUM_CACHED_BLOCKS > 1
297 // By default, pick the last (least recently used) item in the pirority queue...
298 int item_id = cache->pri[MFAT_NUM_CACHED_BLOCKS - 1];
300 // ...but override it if we have a cache hit.
301 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
302 mfat_cached_block_t* cb = &cache->block[i];
303 if (cb->state != MFAT_INVALID && cb->blk_no == blk_no) {
304 item_id = i;
305 break;
309 // Move the selected item to the front of the priority queue (i.e. it's the most recently used
310 // cache item).
312 int prev_id = item_id;
313 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
314 int this_id = cache->pri[i];
315 cache->pri[i] = prev_id;
316 if (this_id == item_id) {
317 break;
319 prev_id = this_id;
323 mfat_cached_block_t* cached_block = &cache->block[item_id];
324 #else
325 mfat_cached_block_t* cached_block = &cache->block[0];
326 #endif
328 // Reassign the cached block to the requested block number (if necessary).
329 if (cached_block->blk_no != blk_no) {
330 #if MFAT_ENABLE_DEBUG
331 if (cached_block->state != MFAT_INVALID) {
332 DBGF("Cache %d: Evicting block %" PRIu32 " in favor of block %" PRIu32,
333 cache_type,
334 cached_block->blk_no,
335 blk_no);
337 #endif
339 #if MFAT_ENABLE_WRITE
340 // Flush the block?
341 if (cached_block->state == MFAT_DIRTY) {
342 DBGF("Cache %d: Flushing evicted block %" PRIu32, cache_type, cached_block->blk_no);
343 if (s_ctx.write((const char*)cached_block->buf, cached_block->blk_no, s_ctx.custom) == -1) {
344 // FATAL: We can't recover from here... :-(
345 DBGF("Cache %d: Failed to flush the block", cache_type);
346 return NULL;
349 #endif
351 // Set the new block ID.
352 cached_block->blk_no = blk_no;
354 // The contents of the buffer is now invalid.
355 cached_block->state = MFAT_INVALID;
358 return cached_block;
361 static mfat_cached_block_t* _mfat_read_block(uint32_t block_no, int cache_type) {
362 // First query the cache.
363 mfat_cached_block_t* block = _mfat_get_cached_block(block_no, cache_type);
364 if (block == NULL) {
365 return NULL;
368 // If necessary, read the block from storage.
369 if (block->state == MFAT_INVALID) {
370 if (s_ctx.read((char*)block->buf, block_no, s_ctx.custom) == -1) {
371 return NULL;
373 block->state = MFAT_VALID;
376 return block;
379 // Helper function for finding the next cluster in a cluster chain.
380 static mfat_bool_t _mfat_next_cluster(const mfat_partition_t* part, uint32_t* cluster) {
381 const uint32_t fat_entry_size = (part->type == MFAT_TYPE_FAT32) ? 4 : 2;
383 uint32_t fat_offset = fat_entry_size * (*cluster);
384 uint32_t fat_block =
385 part->first_block + part->num_reserved_blocks + (fat_offset / MFAT_BLOCK_SIZE);
386 uint32_t fat_block_offset = fat_offset % MFAT_BLOCK_SIZE;
388 // For FAT copy no. N (0..num_fats-1):
389 // fat_block += N * part->blocks_per_fat
391 // Read the FAT block into a cached buffer.
392 mfat_cached_block_t* block = _mfat_read_block(fat_block, MFAT_CACHE_FAT);
393 if (block == NULL) {
394 DBGF("Failed to read the FAT block %" PRIu32, fat_block);
395 return false;
397 uint8_t* buf = &block->buf[0];
399 // Get the value for this cluster from the FAT.
400 uint32_t next_cluster;
401 if (part->type == MFAT_TYPE_FAT32) {
402 // For FAT32 we mask off upper 4 bits, as the cluster number is 28 bits.
403 next_cluster = _mfat_get_dword(&buf[fat_block_offset]) & 0x0fffffffU;
404 } else {
405 next_cluster = _mfat_get_word(&buf[fat_block_offset]);
406 if (next_cluster >= 0xfff7U) {
407 // Convert FAT16 special codes (BAD & EOC) to FAT32 codes.
408 next_cluster |= 0x0fff0000U;
412 // This is a sanity check (failure indicates a corrupt filesystem). We should really do this check
413 // BEFORE accessing the cluster instead.
414 // We do not expect to see:
415 // 0x00000000 - free cluster
416 // 0x0ffffff7 - BAD cluster
417 if (next_cluster == 0U || next_cluster == 0x0ffffff7U) {
418 DBGF("Unexpected next cluster: 0x%08" PRIx32, next_cluster);
419 return false;
422 DBGF("Next cluster: %" PRIu32 " (0x%08" PRIx32 ")", next_cluster, next_cluster);
424 // Return the next cluster number.
425 *cluster = next_cluster;
427 return true;
430 // Helper function for finding the first block of a cluster.
431 static uint32_t _mfat_first_block_of_cluster(const mfat_partition_t* part, uint32_t cluster) {
432 return part->first_data_block + ((cluster - 2U) * part->blocks_per_cluster);
435 // Initialize a cluster pos object to the current cluster & offset (e.g. for a file).
436 static mfat_cluster_pos_t _mfat_cluster_pos_init(const mfat_partition_t* part,
437 const uint32_t cluster_no,
438 const uint32_t offset) {
439 mfat_cluster_pos_t cpos;
440 cpos.cluster_no = cluster_no;
441 cpos.block_in_cluster = (offset % (part->blocks_per_cluster * MFAT_BLOCK_SIZE)) / MFAT_BLOCK_SIZE;
442 cpos.cluster_start_blk = _mfat_first_block_of_cluster(part, cluster_no);
443 return cpos;
446 // Initialize a cluster pos object to the current file offset of the specified file.
447 static mfat_cluster_pos_t _mfat_cluster_pos_init_from_file(const mfat_partition_t* part,
448 const mfat_file_t* f) {
449 return _mfat_cluster_pos_init(part, f->current_cluster, f->offset);
452 // Advance a cluster pos by one block.
453 static mfat_bool_t _mfat_cluster_pos_advance(mfat_cluster_pos_t* cpos,
454 const mfat_partition_t* part) {
455 ++cpos->block_in_cluster;
456 if (cpos->block_in_cluster == part->blocks_per_cluster) {
457 if (!_mfat_next_cluster(part, &cpos->cluster_no)) {
458 return false;
460 cpos->cluster_start_blk = _mfat_first_block_of_cluster(part, cpos->cluster_no);
461 cpos->block_in_cluster = 0;
463 return true;
466 // Get the current absolute block of a cluster pos object.
467 static uint32_t _mfat_cluster_pos_blk_no(const mfat_cluster_pos_t* cpos) {
468 return cpos->cluster_start_blk + cpos->block_in_cluster;
471 // Check for EOC (End of clusterchain). NOTE: EOC cluster is included in file.
472 static mfat_bool_t _mfat_is_eoc(const uint32_t cluster) {
473 return cluster >= 0x0ffffff8U;
476 #if MFAT_ENABLE_GPT
477 static mfat_bool_t _mfat_decode_gpt(void) {
478 // Read the primary GUID Partition Table (GPT) header, located at block 1.
479 mfat_cached_block_t* block = _mfat_read_block(1U, MFAT_CACHE_DATA);
480 if (block == NULL) {
481 DBG("Failed to read the GPT");
482 return false;
484 uint8_t* buf = &block->buf[0];
486 // Is this in fact an GPT header?
487 // TODO(m): We could do more validation (CRC etc).
488 static const uint8_t gpt_sig[] = {0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54};
489 if (!_mfat_cmpbuf(&buf[0], &gpt_sig[0], sizeof(gpt_sig))) {
490 DBG("Not a valid GPT signature");
491 return false;
494 // Get the start LBA of the the partition entries.
495 // Note: This is actually a 64-bit value, but we don't support such large media anyway, and by
496 // reading it out as a 32-bit little endian number we get the correct value IF the upper 32
497 // bits are zero. This should be OK as the value should always be 0x0000000000000002 for the
498 // primary copy, which we are interested in.
499 uint32_t entries_block = _mfat_get_dword(&buf[72]);
501 // Get the number of partition entries.
502 uint32_t num_entries = _mfat_get_dword(&buf[80]);
504 // Get the size of each partition entry, in bytes.
505 uint32_t entry_size = _mfat_get_dword(&buf[84]);
507 DBGF("GPT header: entries_block=%" PRIu32 ", num_entries=%" PRIu32 ", entry_size=%" PRIu32,
508 entries_block,
509 num_entries,
510 entry_size);
512 uint32_t entry_offs = 0;
513 for (uint32_t i = 0; i < num_entries && i < MFAT_NUM_PARTITIONS; ++i) {
514 // Read the next block of the partition entry array if necessary.
515 if ((entry_offs % MFAT_BLOCK_SIZE) == 0) {
516 block = _mfat_read_block(entries_block, MFAT_CACHE_DATA);
517 if (block == NULL) {
518 DBGF("Failed to read the GPT partition entry array at block %" PRIu32, entries_block);
519 return false;
521 buf = &block->buf[0];
523 ++entries_block;
524 entry_offs = 0;
527 // Decode the partition entry.
528 uint8_t* entry = &buf[entry_offs];
529 mfat_partition_t* part = &s_ctx.partition[i];
531 // Get the starting block of the partition (again, 64-bit value that we read as a 32-bit
532 // value).
533 part->first_block = _mfat_get_dword(&entry[32]);
535 // Check if the partition is bootable (bit ).
536 part->boot = ((entry[48] & 0x04) != 0);
538 // Is this a potential FAT partition?
539 if (_mfat_is_fat_part_guid(&entry[0])) {
540 // The actual FAT type is determined later.
541 part->type = MFAT_TYPE_FAT_UNDECIDED;
544 // Note: We print GUIDs in byte order, as we expect them in our signatures, NOT in GUID
545 // mixed endian order.
546 DBGF("GPT entry %" PRIu32 ": first_block = %" PRIu32 ", GUID = %08" PRIx32 "%08" PRIx32
547 "%08" PRIx32 "%08" PRIx32 ", FAT = %s",
549 part->first_block,
550 _mfat_get_dword(&entry[12]),
551 _mfat_get_dword(&entry[8]),
552 _mfat_get_dword(&entry[4]),
553 _mfat_get_dword(&entry[0]),
554 part->type != MFAT_TYPE_UNKNOWN ? "Yes" : "No");
556 entry_offs += entry_size;
559 return true;
561 #endif // MFAT_ENABLE_GPT
563 #if MFAT_ENABLE_MBR
564 static mfat_bool_t _mfat_decode_mbr(void) {
565 mfat_cached_block_t* block = _mfat_read_block(0U, MFAT_CACHE_DATA);
566 if (block == NULL) {
567 DBG("Failed to read the MBR");
568 return false;
570 uint8_t* buf = &block->buf[0];
572 // Is this an MBR block (can also be a partition boot record)?
573 mfat_bool_t found_valid_mbr = false;
574 if (buf[510] == 0x55U && buf[511] == 0xaaU) {
575 // Parse each partition entry.
576 for (int i = 0; i < 4 && i < MFAT_NUM_PARTITIONS; ++i) {
577 uint8_t* entry = &buf[446U + 16U * (uint32_t)i];
578 mfat_partition_t* part = &s_ctx.partition[i];
580 // Current state of partition (00h=Inactive, 80h=Active).
581 part->boot = ((entry[0] & 0x80U) != 0);
583 // Is this a FAT partition.
584 if (_mfat_is_fat_part_id(entry[4])) {
585 // The actual FAT type is determined later.
586 part->type = MFAT_TYPE_FAT_UNDECIDED;
587 found_valid_mbr = true;
590 // Get the beginning of the partition (LBA).
591 part->first_block = _mfat_get_dword(&entry[8]);
593 } else {
594 DBGF("Not a valid MBR signature: [%02x,%02x]", buf[510], buf[511]);
597 return found_valid_mbr;
599 #endif // MFAT_ENABLE_MBR
601 static void _mfat_decode_tableless(void) {
602 // Some storage media are formatted without an MBR or GPT. If so, there is only a single volume
603 // and the first block is the BPB (BIOS Parameter Block) of that "partition". We initially guess
604 // that this is the case, and let the partition decoding logic assert if this was a good guess.
605 DBG("Assuming that the storage medium does not have a partition table");
607 // Clear all partitions (their values are potentially garbage).
608 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
609 memset(&s_ctx.partition[i], 0, sizeof(mfat_partition_t));
612 // Guess that the first partition is FAT (we'll detect the actual type later).
613 // Note: The "first_block" field is cleared to zero in the previous memset, so the first
614 // partition starts at the first block, which is what we intend.
615 s_ctx.partition[0].type = MFAT_TYPE_FAT_UNDECIDED;
618 static mfat_bool_t _mfat_decode_partition_tables(void) {
619 mfat_bool_t found_partition_table = false;
621 #if MFAT_ENABLE_GPT
622 // 1: Try to read the GUID Partition Table (GPT).
623 if (!found_partition_table) {
624 found_partition_table = _mfat_decode_gpt();
626 #endif
628 #if MFAT_ENABLE_MBR
629 // 2: Try to read the Master Boot Record (MBR).
630 if (!found_partition_table) {
631 found_partition_table = _mfat_decode_mbr();
633 #endif
635 // 3: Assume that the storage medium does not have a partition table at all.
636 if (!found_partition_table) {
637 _mfat_decode_tableless();
640 // Read and parse the BPB for each FAT partition.
641 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
642 DBGF("Partition %d:", i);
643 mfat_partition_t* part = &s_ctx.partition[i];
645 // Skip unsupported partition types.
646 if (part->type == MFAT_TYPE_UNKNOWN) {
647 DBG("\t\tNot a FAT partition");
648 continue;
651 // Load the BPB (the first block of the partition).
652 mfat_cached_block_t* block = _mfat_read_block(part->first_block, MFAT_CACHE_DATA);
653 if (block == NULL) {
654 DBG("\t\tFailed to read the BPB");
655 return false;
657 uint8_t* buf = &block->buf[0];
659 if (!_mfat_is_valid_bpb(buf)) {
660 DBG("\t\tPartition does not appear to have a valid BPB");
661 part->type = MFAT_TYPE_UNKNOWN;
662 continue;
665 // Decode useful metrics from the BPB (these are used in block number arithmetic etc).
666 uint32_t bytes_per_block = _mfat_get_word(&buf[11]);
667 part->blocks_per_cluster = buf[13];
668 part->num_reserved_blocks = _mfat_get_word(&buf[14]);
669 part->num_fats = buf[16];
671 uint32_t tot_sectors_16 = _mfat_get_word(&buf[19]);
672 uint32_t tot_sectors_32 = _mfat_get_dword(&buf[32]);
673 part->num_blocks = (tot_sectors_16 != 0) ? tot_sectors_16 : tot_sectors_32;
676 uint32_t blocks_per_fat_16 = _mfat_get_word(&buf[22]);
677 uint32_t blocks_per_fat_32 = _mfat_get_dword(&buf[36]);
678 part->blocks_per_fat = (blocks_per_fat_16 != 0) ? blocks_per_fat_16 : blocks_per_fat_32;
681 uint32_t num_root_entries = _mfat_get_word(&buf[17]);
682 part->blocks_in_root_dir =
683 ((num_root_entries * 32U) + (MFAT_BLOCK_SIZE - 1U)) / MFAT_BLOCK_SIZE;
686 // Derive useful metrics.
687 part->first_data_block = part->first_block + part->num_reserved_blocks +
688 (part->num_fats * part->blocks_per_fat) + part->blocks_in_root_dir;
690 // Check that the number of bytes per sector is 512.
691 // TODO(m): We could add support for larger block sizes.
692 if (bytes_per_block != MFAT_BLOCK_SIZE) {
693 DBGF("\t\tUnsupported block size: %" PRIu32, bytes_per_block);
694 part->type = MFAT_TYPE_UNKNOWN;
695 continue;
698 // Up until now we have only been guessing the partition FAT type. Now we determine the *actual*
699 // partition type (FAT12, FAT16 or FAT32), using the detection algorithm given in the FAT32 File
700 // System Specification, p.14. The definition is based on the count of clusters as follows:
701 // FAT12: count of clusters < 4085
702 // FAT16: 4085 <= count of clusters < 65525
703 // FAT32: 65525 <= count of clusters
705 uint32_t root_ent_cnt = _mfat_get_word(&buf[17]);
706 uint32_t root_dir_sectors = ((root_ent_cnt * 32) + (MFAT_BLOCK_SIZE - 1)) / MFAT_BLOCK_SIZE;
708 uint32_t data_sectors =
709 part->num_blocks -
710 (part->num_reserved_blocks + (part->num_fats * part->blocks_per_fat) + root_dir_sectors);
712 uint32_t count_of_clusters = data_sectors / part->blocks_per_cluster;
714 #if MFAT_ENABLE_WRITE
715 // We need to know the actual number of clusters when writing files.
716 part->num_clusters = count_of_clusters + 1;
717 #endif
719 // We don't support FAT12.
720 if (count_of_clusters < 4085) {
721 part->type = MFAT_TYPE_UNKNOWN;
722 DBG("\t\tFAT12 is not supported.");
723 continue;
726 if (count_of_clusters < 65525) {
727 part->type = MFAT_TYPE_FAT16;
728 } else {
729 part->type = MFAT_TYPE_FAT32;
733 // Determine the starting block/cluster for the root directory.
734 if (part->type == MFAT_TYPE_FAT16) {
735 part->root_dir_block = part->first_data_block - part->blocks_in_root_dir;
736 } else {
737 part->root_dir_cluster = _mfat_get_dword(&buf[44]);
740 #if MFAT_ENABLE_DEBUG
741 // Print the partition information.
742 DBGF("\t\ttype = %s", part->type == MFAT_TYPE_FAT16 ? "FAT16" : "FAT32");
743 DBGF("\t\tboot = %s", part->boot ? "Yes" : "No");
744 DBGF("\t\tfirst_block = %" PRIu32, part->first_block);
745 DBGF("\t\tbytes_per_block = %" PRIu32, bytes_per_block);
746 DBGF("\t\tnum_blocks = %" PRIu32, part->num_blocks);
747 DBGF("\t\tblocks_per_cluster = %" PRIu32, part->blocks_per_cluster);
748 #if MFAT_ENABLE_WRITE
749 DBGF("\t\tnum_clusters = %" PRIu32, part->num_clusters);
750 #endif
751 DBGF("\t\tblocks_per_fat = %" PRIu32, part->blocks_per_fat);
752 DBGF("\t\tnum_fats = %" PRIu32, part->num_fats);
753 DBGF("\t\tnum_reserved_blocks = %" PRIu32, part->num_reserved_blocks);
754 DBGF("\t\troot_dir_block = %" PRIu32, part->root_dir_block);
755 DBGF("\t\troot_dir_cluster = %" PRIu32, part->root_dir_cluster);
756 DBGF("\t\tblocks_in_root_dir = %" PRIu32, part->blocks_in_root_dir);
757 DBGF("\t\tfirst_data_block = %" PRIu32, part->first_data_block);
759 // Print the extended boot signature.
760 const uint8_t* ex_boot_sig = (part->type == MFAT_TYPE_FAT32) ? &buf[66] : &buf[38];
761 if (ex_boot_sig[0] == 0x29) {
762 uint32_t vol_id = _mfat_get_dword(&ex_boot_sig[1]);
764 char label[12];
765 memcpy(&label[0], &ex_boot_sig[5], 11);
766 label[11] = 0;
768 char fs_type[9];
769 memcpy(&fs_type[0], &ex_boot_sig[16], 8);
770 fs_type[8] = 0;
772 DBGF("\t\tvol_id = %04" PRIx32 ":%04" PRIx32, (vol_id >> 16), vol_id & 0xffffU);
773 DBGF("\t\tlabel = \"%s\"", label);
774 DBGF("\t\tfs_type = \"%s\"", fs_type);
775 } else {
776 DBGF("\t\t(Boot signature N/A - bpb[%d]=0x%02x)", (int)(ex_boot_sig - buf), ex_boot_sig[0]);
778 #endif // MFAT_ENABLE_DEBUG
781 return true;
784 static mfat_file_t* _mfat_get_file(int fd) {
785 if (fd < 0 || fd >= MFAT_NUM_FDS) {
786 DBGF("FD out of range: %d", fd);
787 return NULL;
789 mfat_file_t* f = &s_ctx.file[fd];
790 if (!f->open) {
791 DBGF("File is not open: %d", fd);
792 return NULL;
794 return f;
797 static void _mfat_dir_entry_to_stat(uint8_t* dir_entry, mfat_stat_t* stat) {
798 // Decode file attributes.
799 uint32_t attr = dir_entry[11];
800 uint32_t st_mode =
801 MFAT_S_IRUSR | MFAT_S_IRGRP | MFAT_S_IROTH | MFAT_S_IXUSR | MFAT_S_IXGRP | MFAT_S_IXOTH;
802 if ((attr & MFAT_ATTR_READ_ONLY) == 0) {
803 st_mode |= MFAT_S_IWUSR | MFAT_S_IWGRP | MFAT_S_IWOTH;
805 if ((attr & MFAT_ATTR_DIRECTORY) != 0) {
806 st_mode |= MFAT_S_IFDIR;
807 } else {
808 st_mode |= MFAT_S_IFREG;
810 stat->st_mode = st_mode;
812 // Decode time.
813 uint32_t time = _mfat_get_word(&dir_entry[22]);
814 stat->st_mtim.hour = time >> 11;
815 stat->st_mtim.minute = (time >> 5) & 63;
816 stat->st_mtim.second = (time & 31) * 2;
818 // Decode date.
819 uint32_t date = _mfat_get_word(&dir_entry[24]);
820 stat->st_mtim.year = (date >> 9) + 1980U;
821 stat->st_mtim.month = (date >> 5) & 15;
822 stat->st_mtim.day = date & 31;
824 // Get file size.
825 stat->st_size = _mfat_get_dword(&dir_entry[28]);
828 static int _mfat_canonicalize_char(int c) {
829 // Valid character?
830 if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '$' || c == '%' || c == '-' ||
831 c == '_' || c == '@' || c == '~' || c == '`' || c == '!' || c == '(' || c == ')' ||
832 c == '{' || c == '}' || c == '^' || c == '#' || c == '&') {
833 return c;
836 // Convert lower case to upper case.
837 if (c >= 'a' && c <= 'z') {
838 return c - 'a' + 'A';
841 // Invalid character.
842 return '!';
845 /// @brief Canonicalize a file name.
847 /// Convert the next path part (up until a directory separator or the end of the string) into a
848 /// valid 8.3 short file name as represented in a FAT directory entry (i.e. space padded and without
849 /// the dot). File names that do not fit in an 8.3 representation will be silently truncated.
851 /// Examples:
852 /// - "hello.txt" -> "HELLO TXT"
853 /// - "File.1" -> "FILE 1 "
854 /// - "ALongFileName.json" -> "ALONGFILJSO"
855 /// - "bin/foo.exe" -> "BIN " (followed by "FOO EXE" at the next invocation)
856 /// - "./foo.exe" -> "FOO EXE"
857 /// @param path A path to a file, possibly includuing directory separators (/ or \).
858 /// @param[out] fname The canonicalized file name.
859 /// @returns the index of the next path part, or -1 if this was the last path part.
860 static int _mfat_canonicalize_fname(const char* path, char name[12]) {
861 int next_idx = 0;
863 do {
864 int pos = next_idx;
865 int npos = 0;
866 int c;
868 // Extract the name part.
869 while (true) {
870 c = (int)(uint8_t)path[pos++];
871 if (c == 0 || c == '.' || c == '/' || c == '\\') {
872 break;
874 if (npos < 8) {
875 name[npos++] = (char)_mfat_canonicalize_char(c);
879 // Space-fill remaining characters of the name part.
880 for (; npos < 8; ++npos) {
881 name[npos] = ' ';
884 // Extract the extension part.
885 if (c == '.') {
886 while (true) {
887 c = (int)(uint8_t)path[pos++];
888 if (c == 0 || c == '/' || c == '\\') {
889 break;
891 if (npos < 11) {
892 name[npos++] = (char)_mfat_canonicalize_char(c);
897 // Space-fill remaining characters of the extension part.
898 for (; npos < 11; ++npos) {
899 name[npos] = ' ';
902 // Zero terminate the string.
903 name[11] = 0;
905 // Was this a directory part of the path (ignore trailing directory separators)?
906 if ((c == '/' || c == '\\') && path[pos] != 0) {
907 // Return the starting position of the next path part.
908 next_idx = pos;
909 } else {
910 // Indicate that there are no more path parts by returning -1.
911 next_idx = -1;
914 // Skip empty "/" and "./" directory references.
915 } while (next_idx >= 0 &&
916 _mfat_cmpbuf((const uint8_t*)&name[0], (const uint8_t*)" ", 11));
918 return next_idx;
921 /// @brief Find a file on the given partition.
923 /// If the directory (if part of the path) exists, but the file does not exist in the directory,
924 /// this function will find the first free directory entry slot and set @c exists to false. This is
925 /// useful for creating new files.
926 /// @param part_no The partition number.
927 /// @param path The absolute path to the file.
928 /// @param[out] info Information about the file.
929 /// @param[out] is_dir true if the file is a directory, false if it is a regular file.
930 /// @param[out] exists true if the file exists, false if it needs to be created.
931 /// @returns true if the file (or its potential slot) was found.
932 static mfat_bool_t _mfat_find_file(int part_no,
933 const char* path,
934 mfat_file_info_t* info,
935 mfat_bool_t* is_dir,
936 mfat_bool_t* exists) {
937 mfat_partition_t* part = &s_ctx.partition[part_no];
939 // Start with the root directory cluster/block.
940 mfat_cluster_pos_t cpos;
941 uint32_t blocks_left;
942 if (part->type == MFAT_TYPE_FAT32) {
943 cpos = _mfat_cluster_pos_init(part, part->root_dir_cluster, 0);
944 blocks_left = 0xffffffffU;
945 } else {
946 // We use a fake/tweaked cluster pos for FAT16 root directories.
947 cpos.cluster_no = 0U;
948 cpos.cluster_start_blk = part->root_dir_block;
949 cpos.block_in_cluster = 0U;
950 blocks_left = part->blocks_in_root_dir;
953 // Try to find the given path.
954 mfat_cached_block_t* block = NULL;
955 uint8_t* file_entry = NULL;
956 int path_pos = 0;
957 while (path_pos >= 0) {
958 // Extract a directory entry compatible file name.
959 char fname[12];
960 int name_pos = _mfat_canonicalize_fname(&path[path_pos], fname);
961 mfat_bool_t is_parent_dir = (name_pos >= 0);
962 path_pos = is_parent_dir ? path_pos + name_pos : -1;
963 DBGF("Looking for %s: \"%s\"", is_parent_dir ? "parent dir" : "file", fname);
965 // Use an "unlimited" block counter if we're doing a clusterchain lookup.
966 if (cpos.cluster_no != 0U) {
967 blocks_left = 0xffffffffU;
970 // Look up the file name in the directory.
971 mfat_bool_t no_more_entries = false;
972 for (; file_entry == NULL && !no_more_entries && blocks_left > 0U; --blocks_left) {
973 // Load the directory table block.
974 block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
975 if (block == NULL) {
976 DBGF("Unable to load directory block %" PRIu32, _mfat_cluster_pos_blk_no(&cpos));
977 return false;
979 uint8_t* buf = &block->buf[0];
981 // Loop over all the files in this directory block.
982 uint8_t* found_entry = NULL;
983 for (uint32_t offs = 0U; offs < 512U; offs += 32U) {
984 uint8_t* entry = &buf[offs];
986 // TODO(m): Look for the first 0xe5-entry too in case we want to create a new file.
988 // Last entry in the directory structure?
989 if (entry[0] == 0x00) {
990 no_more_entries = true;
991 break;
994 // Is this the file/dir that we are looking for?
995 if (_mfat_cmpbuf(&entry[0], (const uint8_t*)&fname[0], 11)) {
996 found_entry = entry;
997 break;
1001 // Did we have a match.
1002 if (found_entry != NULL) {
1003 uint32_t attr = found_entry[11];
1005 // Descend into directory?
1006 if (is_parent_dir) {
1007 if ((attr & MFAT_ATTR_DIRECTORY) == 0U) {
1008 DBGF("Not a directory: %s", fname);
1009 return false;
1012 // Decode the starting cluster of the child directory entry table.
1013 uint32_t chile_dir_cluster_no =
1014 (_mfat_get_word(&found_entry[20]) << 16) | _mfat_get_word(&found_entry[26]);
1015 cpos = _mfat_cluster_pos_init(part, chile_dir_cluster_no, 0);
1016 blocks_left = 0xffffffffU;
1017 } else {
1018 file_entry = found_entry;
1021 break;
1024 // Go to the next block in the directory.
1025 if (cpos.cluster_no != 0U) {
1026 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1027 return false;
1029 } else {
1030 cpos.block_in_cluster += 1; // FAT16 style linear block access.
1034 // Break loop if we didn't find the file.
1035 if (no_more_entries) {
1036 break;
1040 // Could we neither find the file nor a new directory slot for the file?
1041 if (file_entry == NULL) {
1042 return false;
1045 // Define the file properties.
1046 info->part_no = part_no;
1047 info->size = _mfat_get_dword(&file_entry[28]);
1048 info->first_cluster = (_mfat_get_word(&file_entry[20]) << 16) | _mfat_get_word(&file_entry[26]);
1049 info->dir_entry_block = block->blk_no;
1050 info->dir_entry_offset = file_entry - block->buf;
1052 // Does the file exist?
1053 if ((file_entry[0] != 0x00) && (file_entry[0] != 0xe5)) {
1054 *is_dir = (file_entry[11] & MFAT_ATTR_DIRECTORY) != 0U;
1055 *exists = true;
1056 } else {
1057 *is_dir = false;
1058 *exists = false;
1061 return true;
1064 #if MFAT_ENABLE_WRITE
1065 static void _mfat_sync_impl() {
1066 for (int j = 0; j < MFAT_NUM_CACHES; ++j) {
1067 mfat_cache_t* cache = &s_ctx.cache[j];
1068 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
1069 mfat_cached_block_t* cb = &cache->block[i];
1070 if (cb->state == MFAT_DIRTY) {
1071 DBGF("Cache: Flushing block %" PRIu32, cb->blk_no);
1072 s_ctx.write((const char*)cb->buf, cb->blk_no, s_ctx.custom);
1073 cb->state = MFAT_VALID;
1078 #endif
1080 static int _mfat_fstat_impl(mfat_file_info_t* info, mfat_stat_t* stat) {
1081 // Read the directory entry block (should already be in the cache).
1082 mfat_cached_block_t* block = _mfat_read_block(info->dir_entry_block, MFAT_CACHE_DATA);
1083 if (block == NULL) {
1084 return -1;
1087 // Extract the relevant information for this directory entry.
1088 uint8_t* dir_entry = &block->buf[info->dir_entry_offset];
1089 _mfat_dir_entry_to_stat(dir_entry, stat);
1091 return 0;
1094 static int _mfat_stat_impl(const char* path, mfat_stat_t* stat) {
1095 // Find the file in the file system structure.
1096 mfat_bool_t is_dir;
1097 mfat_bool_t exists;
1098 mfat_file_info_t info;
1099 mfat_bool_t ok = _mfat_find_file(s_ctx.active_partition, path, &info, &is_dir, &exists);
1100 if (!ok || !exists) {
1101 DBGF("File not found: %s", path);
1102 return -1;
1105 return _mfat_fstat_impl(&info, stat);
1108 static int _mfat_open_impl(const char* path, int oflag) {
1109 // Find the next free fd.
1110 int fd;
1111 for (fd = 0; fd < MFAT_NUM_FDS; ++fd) {
1112 if (!s_ctx.file[fd].open) {
1113 break;
1116 if (fd >= MFAT_NUM_FDS) {
1117 DBG("No free FD:s left");
1118 return -1;
1120 mfat_file_t* f = &s_ctx.file[fd];
1122 // Find the file in the file system structure.
1123 mfat_bool_t is_dir;
1124 mfat_bool_t exists;
1125 if (!_mfat_find_file(s_ctx.active_partition, path, &f->info, &is_dir, &exists)) {
1126 DBGF("File not found: %s", path);
1127 return -1;
1130 // Check that we found the correct type (i.e. regular file).
1131 if (is_dir) {
1132 DBGF("Can not open the directory: %s", path);
1133 return -1;
1136 // Handle non-existing files.
1137 if (!exists) {
1138 #if MFAT_ENABLE_WRITE
1139 // Should we create the file?
1140 if ((oflag & MFAT_O_CREAT) != 0U) {
1141 DBG("Creating files is not yet implemented");
1142 return -1;
1144 #endif
1146 DBGF("File does not exist: %s", path);
1147 return -1;
1150 // Initialize the file state.
1151 f->open = true;
1152 f->oflag = oflag;
1153 f->current_cluster = f->info.first_cluster;
1154 f->offset = 0U;
1156 DBGF("Opening file: first_cluster = %" PRIu32 " (block = %" PRIu32 "), size = %" PRIu32
1157 " bytes, dir_blk = %" PRIu32
1158 ", dir_offs "
1159 "= %" PRIu32,
1160 f->info.first_cluster,
1161 _mfat_first_block_of_cluster(&s_ctx.partition[f->info.part_no], f->info.first_cluster),
1162 f->info.size,
1163 f->info.dir_entry_block,
1164 f->info.dir_entry_offset);
1166 return fd;
1169 static int _mfat_close_impl(mfat_file_t* f) {
1170 #if MFAT_ENABLE_WRITE
1171 // For good measure, we flush pending writes when a file is closed (only do this when closing
1172 // files that are open with write permissions).
1173 if ((f->oflag & MFAT_O_WRONLY) != 0) {
1174 _mfat_sync_impl();
1176 #endif
1178 // The file is no longer open. This makes the fd available for future open() requests.
1179 f->open = false;
1181 return 0;
1184 static int64_t _mfat_read_impl(mfat_file_t* f, uint8_t* buf, uint32_t nbyte) {
1185 // Is the file open with read permissions?
1186 if ((f->oflag & MFAT_O_RDONLY) == 0) {
1187 return -1;
1190 // Determine actual size of the operation (clamp to the size of the file).
1191 if (nbyte > (f->info.size - f->offset)) {
1192 nbyte = f->info.size - f->offset;
1193 DBGF("read: Clamped read request to %" PRIu32 " bytes", nbyte);
1196 // Early out if only zero bytes were requested (e.g. if we are at the EOF).
1197 if (nbyte == 0U) {
1198 return 0;
1201 // Start out at the current file offset.
1202 mfat_partition_t* part = &s_ctx.partition[f->info.part_no];
1203 mfat_cluster_pos_t cpos = _mfat_cluster_pos_init_from_file(part, f);
1204 uint32_t bytes_read = 0U;
1206 // Align the head of the operation to a block boundary.
1207 uint32_t block_offset = f->offset % MFAT_BLOCK_SIZE;
1208 if (block_offset != 0U) {
1209 // Use the block cache to get a partial block.
1210 mfat_cached_block_t* block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1211 if (block == NULL) {
1212 DBG("Unable to read block");
1213 return -1;
1216 // Copy the data from the cache to the target buffer.
1217 uint32_t tail_bytes_in_block = MFAT_BLOCK_SIZE - block_offset;
1218 uint32_t bytes_to_copy = _mfat_min(tail_bytes_in_block, nbyte);
1219 memcpy(buf, &block->buf[block_offset], bytes_to_copy);
1220 DBGF("read: Head read of %" PRIu32 " bytes", bytes_to_copy);
1222 buf += bytes_to_copy;
1223 bytes_read += bytes_to_copy;
1225 // Move to the next block if we have read all the bytes of the block.
1226 if (bytes_to_copy == tail_bytes_in_block) {
1227 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1228 return -1;
1233 // Read aligned blocks directly into the target buffer.
1234 while ((nbyte - bytes_read) >= MFAT_BLOCK_SIZE) {
1235 if (_mfat_is_eoc(cpos.cluster_no)) {
1236 DBG("Unexpected cluster access after EOC");
1237 return -1;
1240 DBGF("read: Direct read of %d bytes", MFAT_BLOCK_SIZE);
1241 if (s_ctx.read((char*)buf, _mfat_cluster_pos_blk_no(&cpos), s_ctx.custom) == -1) {
1242 DBG("Unable to read block");
1243 return -1;
1245 buf += MFAT_BLOCK_SIZE;
1246 bytes_read += MFAT_BLOCK_SIZE;
1248 // Move to the next block.
1249 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1250 return -1;
1254 // Handle the tail of the operation (unaligned tail).
1255 if (bytes_read < nbyte) {
1256 if (_mfat_is_eoc(cpos.cluster_no)) {
1257 DBG("Unexpected cluster access after EOC");
1258 return -1;
1261 // Use the block cache to get a partial block.
1262 mfat_cached_block_t* block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1263 if (block == NULL) {
1264 DBG("Unable to read block");
1265 return -1;
1268 // Copy the data from the cache to the target buffer.
1269 uint32_t bytes_to_copy = nbyte - bytes_read;
1270 memcpy(buf, &block->buf[0], bytes_to_copy);
1271 DBGF("read: Tail read of %" PRIu32 " bytes", bytes_to_copy);
1273 bytes_read += bytes_to_copy;
1276 // Update file state.
1277 f->current_cluster = cpos.cluster_no;
1278 f->offset += bytes_read;
1280 return bytes_read;
1283 #if MFAT_ENABLE_WRITE
1284 static int64_t _mfat_write_impl(mfat_file_t* f, const uint8_t* buf, uint32_t nbyte) {
1285 // Is the file open with write permissions?
1286 if ((f->oflag & MFAT_O_WRONLY) == 0) {
1287 return -1;
1290 // TODO(m): Implement me!
1291 (void)buf;
1292 (void)nbyte;
1293 DBG("_mfat_write_impl() - not yet implemented");
1294 return -1;
1296 #endif
1298 static int64_t _mfat_lseek_impl(mfat_file_t* f, int64_t offset, int whence) {
1299 // Calculate the new requested file offset (the arithmetic is done in 64-bit precision in order to
1300 // properly handle overflow).
1301 int64_t target_offset_64;
1302 switch (whence) {
1303 case MFAT_SEEK_SET:
1304 target_offset_64 = offset;
1305 break;
1306 case MFAT_SEEK_END:
1307 target_offset_64 = ((int64_t)f->info.size) + offset;
1308 break;
1309 case MFAT_SEEK_CUR:
1310 target_offset_64 = ((int64_t)f->offset) + offset;
1311 break;
1312 default:
1313 DBGF("Invalid whence: %d", whence);
1314 return -1;
1317 // Sanity check the new offset.
1318 if (target_offset_64 < 0) {
1319 DBG("Seeking to a negative offset is not allowed");
1320 return -1;
1322 if (target_offset_64 > (int64_t)f->info.size) {
1323 // TODO(m): POSIX lseek() allows seeking beyond the end of the file. We currently don't.
1324 DBG("Seeking beyond the end of the file is not allowed");
1325 return -1;
1328 // This type conversion is safe, since we have verified that the value fits in a 32 bit unsigned
1329 // value.
1330 uint32_t target_offset = (uint32_t)target_offset_64;
1332 // Get partition info.
1333 mfat_partition_t* part = &s_ctx.partition[f->info.part_no];
1334 uint32_t bytes_per_cluster = part->blocks_per_cluster * MFAT_BLOCK_SIZE;
1336 // Define the starting point for the cluster search.
1337 uint32_t current_cluster = f->current_cluster;
1338 uint32_t cluster_offset = f->offset - (f->offset % bytes_per_cluster);
1339 if (target_offset < cluster_offset) {
1340 // For reverse seeking we need to start from the beginning of the file since FAT uses singly
1341 // linked lists.
1342 current_cluster = f->info.first_cluster;
1343 cluster_offset = 0;
1346 // Skip along clusters until we find the cluster that contains the requested offset.
1347 while ((target_offset - cluster_offset) >= bytes_per_cluster) {
1348 if (_mfat_is_eoc(current_cluster)) {
1349 DBG("Unexpected cluster access after EOC");
1350 return -1;
1353 // Look up the next cluster.
1354 if (!_mfat_next_cluster(part, &current_cluster)) {
1355 return -1;
1357 cluster_offset += bytes_per_cluster;
1360 // Update the current offset in the file descriptor.
1361 f->offset = target_offset;
1362 f->current_cluster = current_cluster;
1364 return (int64_t)target_offset;
1367 //--------------------------------------------------------------------------------------------------
1368 // Public API functions.
1369 //--------------------------------------------------------------------------------------------------
1371 int mfat_mount(mfat_read_block_fun_t read_fun, mfat_write_block_fun_t write_fun, void* custom) {
1372 #if MFAT_ENABLE_WRITE
1373 if (read_fun == NULL || write_fun == NULL) {
1374 #else
1375 if (read_fun == NULL) {
1376 #endif
1377 DBG("Bad function pointers");
1378 return -1;
1381 // Clear the context state.
1382 memset(&s_ctx, 0, sizeof(mfat_ctx_t));
1383 s_ctx.read = read_fun;
1384 #if MFAT_ENABLE_WRITE
1385 s_ctx.write = write_fun;
1386 #else
1387 (void)write_fun;
1388 #endif
1389 s_ctx.custom = custom;
1390 s_ctx.active_partition = -1;
1392 #if MFAT_NUM_CACHED_BLOCKS > 1
1393 // Initialize the block cache priority queues.
1394 for (int j = 0; j < MFAT_NUM_CACHES; ++j) {
1395 mfat_cache_t* cache = &s_ctx.cache[j];
1396 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
1397 // Assign initial items to the priority queue.
1398 cache->pri[i] = i;
1401 #endif
1403 // Read the partition tables.
1404 if (!_mfat_decode_partition_tables()) {
1405 return -1;
1408 // Find the first bootable partition. If no bootable partition is found, pick the first supported
1409 // partition.
1410 int first_boot_partition = -1;
1411 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
1412 if (s_ctx.partition[i].type != MFAT_TYPE_UNKNOWN) {
1413 if (s_ctx.partition[i].boot && first_boot_partition < 0) {
1414 first_boot_partition = i;
1415 s_ctx.active_partition = i;
1416 } else if (s_ctx.active_partition < 0) {
1417 s_ctx.active_partition = i;
1421 if (s_ctx.active_partition < 0) {
1422 return -1;
1424 DBGF("Selected partition %d", s_ctx.active_partition);
1426 // MFAT is now initizlied.
1427 DBG("Successfully initialized");
1428 s_ctx.initialized = true;
1430 return 0;
1433 void mfat_unmount(void) {
1434 #if MFAT_ENABLE_WRITE
1435 // Flush any pending writes.
1436 _mfat_sync_impl();
1437 #endif
1438 s_ctx.initialized = false;
1441 int mfat_select_partition(int partition_no) {
1442 if (!s_ctx.initialized) {
1443 DBG("Not initialized");
1444 return -1;
1446 if (partition_no < 0 || partition_no >= MFAT_NUM_PARTITIONS) {
1447 DBGF("Bad partition number: %d", partition_no);
1448 return -1;
1450 if (s_ctx.partition[partition_no].type == MFAT_TYPE_UNKNOWN) {
1451 DBG("Unsupported partition type");
1452 return -1;
1455 s_ctx.active_partition = partition_no;
1457 return 0;
1460 void mfat_sync(void) {
1461 #if MFAT_ENABLE_WRITE
1462 if (!s_ctx.initialized) {
1463 DBG("Not initialized");
1464 return;
1467 _mfat_sync_impl();
1468 #endif
1471 int mfat_fstat(int fd, mfat_stat_t* stat) {
1472 if (!s_ctx.initialized) {
1473 DBG("Not initialized");
1474 return -1;
1477 mfat_file_t* f = _mfat_get_file(fd);
1478 if (f == NULL) {
1479 return -1;
1482 return _mfat_fstat_impl(&f->info, stat);
1485 int mfat_stat(const char* path, mfat_stat_t* stat) {
1486 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1487 DBG("Not initialized");
1488 return -1;
1490 if (path == NULL || stat == NULL) {
1491 return -1;
1494 return _mfat_stat_impl(path, stat);
1497 int mfat_open(const char* path, int oflag) {
1498 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1499 DBG("Not initialized");
1500 return -1;
1502 if (path == NULL || ((oflag & MFAT_O_RDWR) == 0)) {
1503 return -1;
1506 return _mfat_open_impl(path, oflag);
1509 int mfat_close(int fd) {
1510 if (!s_ctx.initialized) {
1511 DBG("Not initialized");
1512 return -1;
1515 mfat_file_t* f = _mfat_get_file(fd);
1516 if (f == NULL) {
1517 return -1;
1520 return _mfat_close_impl(f);
1523 int64_t mfat_read(int fd, void* buf, uint32_t nbyte) {
1524 if (!s_ctx.initialized) {
1525 DBG("Not initialized");
1526 return -1;
1529 mfat_file_t* f = _mfat_get_file(fd);
1530 if (f == NULL) {
1531 return -1;
1534 return _mfat_read_impl(f, (uint8_t*)buf, nbyte);
1537 int64_t mfat_write(int fd, const void* buf, uint32_t nbyte) {
1538 #if MFAT_ENABLE_WRITE
1539 if (!s_ctx.initialized) {
1540 DBG("Not initialized");
1541 return -1;
1544 mfat_file_t* f = _mfat_get_file(fd);
1545 if (f == NULL) {
1546 return -1;
1549 return _mfat_write_impl(f, (const uint8_t*)buf, nbyte);
1550 #else
1551 return -1;
1552 #endif
1555 int64_t mfat_lseek(int fd, int64_t offset, int whence) {
1556 if (!s_ctx.initialized) {
1557 DBG("Not initialized");
1558 return -1;
1561 mfat_file_t* f = _mfat_get_file(fd);
1562 if (f == NULL) {
1563 return -1;
1566 return _mfat_lseek_impl(f, offset, whence);