Update README.md
[mfat.git] / mfat.c
blobcbc0c7d3439d7e9978584014ec1e7145be371927
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 directory reading (opendir etc)?
44 #ifndef MFAT_ENABLE_OPENDIR
45 #define MFAT_ENABLE_OPENDIR 1
46 #endif
48 // Enable MBR support?
49 #ifndef MFAT_ENABLE_MBR
50 #define MFAT_ENABLE_MBR 1
51 #endif
53 // Enable GPT support?
54 #ifndef MFAT_ENABLE_GPT
55 #define MFAT_ENABLE_GPT 1
56 #endif
58 // Number of cached blocks.
59 #ifndef MFAT_NUM_CACHED_BLOCKS
60 #define MFAT_NUM_CACHED_BLOCKS 2
61 #endif
63 // Maximum number of open file descriptors.
64 #ifndef MFAT_NUM_FDS
65 #define MFAT_NUM_FDS 4
66 #endif
68 // Maximum number of open directories.
69 #ifndef MFAT_NUM_DIRS
70 #define MFAT_NUM_DIRS 2
71 #endif
73 // Maximum number of partitions to support.
74 #ifndef MFAT_NUM_PARTITIONS
75 #define MFAT_NUM_PARTITIONS 4
76 #endif
78 //--------------------------------------------------------------------------------------------------
79 // Debugging macros.
80 //--------------------------------------------------------------------------------------------------
82 #if MFAT_ENABLE_DEBUG
83 #include <stdio.h>
84 #define DBG(_str) (void)fprintf(stderr, "[MFAT] " _str "\n")
85 #define DBGF(_fmt, ...) (void)fprintf(stderr, "[MFAT] " _fmt "\n", __VA_ARGS__)
86 #else
87 #define DBG(_str)
88 #define DBGF(_fmt, ...)
89 #endif // MFAT_ENABLE_DEBUG
91 //--------------------------------------------------------------------------------------------------
92 // FAT definitions (used for encoding/decoding).
93 //--------------------------------------------------------------------------------------------------
95 // Known partition ID:s.
96 #define MFAT_PART_ID_NONE 0x00
97 #define MFAT_PART_ID_FAT16_LT32GB 0x04
98 #define MFAT_PART_ID_FAT16_GT32GB 0x06
99 #define MFAT_PART_ID_FAT32 0x0b
100 #define MFAT_PART_ID_FAT32_LBA 0x0c
101 #define MFAT_PART_ID_FAT16_GT32GB_LBA 0x0e
103 // File attribute flags.
104 #define MFAT_ATTR_READ_ONLY 0x01
105 #define MFAT_ATTR_HIDDEN 0x02
106 #define MFAT_ATTR_SYSTEM 0x04
107 #define MFAT_ATTR_VOLUME_ID 0x08
108 #define MFAT_ATTR_DIRECTORY 0x10
109 #define MFAT_ATTR_ARCHIVE 0x20
110 #define MFAT_ATTR_LONG_NAME 0x0f
112 //--------------------------------------------------------------------------------------------------
113 // Private types and constants.
114 //--------------------------------------------------------------------------------------------------
116 // A custom boolean type (stdbool is byte-sized which is unnecessarily performance costly).
117 typedef int mfat_bool_t;
118 #define false 0
119 #define true 1
121 // mfat_cached_block_t::state
122 #define MFAT_INVALID 0
123 #define MFAT_VALID 1
124 #define MFAT_DIRTY 2
126 // Different types of caches (we keep independent block types in different caches).
127 #define MFAT_CACHE_DATA 0
128 #define MFAT_CACHE_FAT 1
129 #define MFAT_NUM_CACHES 2
131 // mfat_partition_t::type
132 #define MFAT_PART_TYPE_UNKNOWN 0
133 #define MFAT_PART_TYPE_FAT_UNDECIDED 1 // A FAT partion of yet unknown type (FAT16 or FAT32).
134 #define MFAT_PART_TYPE_FAT16 2
135 #define MFAT_PART_TYPE_FAT32 3
137 // mfat_file_t::type
138 #define MFAT_FILE_TYPE_REGULAR 0 // Regular file.
139 #define MFAT_FILE_TYPE_DIR 1 // A directory.
140 #define MFAT_FILE_TYPE_FAT16ROOTDIR 2 // A FAT16 root directory (which is special).
142 // A collection of variables for keeping track of the current cluster & block position, e.g. during
143 // read/write operations.
144 typedef struct {
145 uint32_t cluster_no; ///< The cluster number.
146 uint32_t block_in_cluster; ///< The block offset within the cluster (0..blocks_per_cluster-1).
147 uint32_t cluster_start_blk; ///< Absolute block number of the first block of the cluster.
148 } mfat_cluster_pos_t;
150 typedef struct {
151 uint32_t type;
152 uint32_t first_block;
153 uint32_t num_blocks;
154 uint32_t blocks_per_cluster;
155 #if MFAT_ENABLE_WRITE
156 uint32_t num_clusters;
157 #endif
158 uint32_t blocks_per_fat;
159 uint32_t num_fats;
160 uint32_t num_reserved_blocks;
161 uint32_t root_dir_block; // Used for FAT16.
162 uint32_t blocks_in_root_dir; // Used for FAT16 (zero for FAT32).
163 uint32_t root_dir_cluster; // Used for FAT32.
164 uint32_t first_data_block;
165 mfat_bool_t boot;
166 } mfat_partition_t;
168 // Static information about a file, as specified in the file system.
169 typedef struct {
170 int part_no; // Partition that this file is located on.
171 uint32_t size; // Total size of file, in bytes.
172 uint32_t first_cluster; // Starting cluster for the file.
173 uint32_t dir_entry_block; // Block number for the directory entry of this file.
174 uint32_t dir_entry_offset; // Offset (in bytes) into the directory entry block.
175 } mfat_file_info_t;
177 // File handle, corresponding to a file descriptor (fd).
178 typedef struct {
179 mfat_bool_t open; // Is the file open?
180 int type; // File type (e.g. MFAT_FILE_TYPE_REGULAR or MFAT_FILE_TYPE_DIR).
181 int oflag; // Flags used when opening the file.
182 uint32_t offset; // Current byte offset relative to the file start (seek offset).
183 uint32_t current_cluster; // Current cluster (representing the current seek offset)
184 mfat_file_info_t info;
185 } mfat_file_t;
187 // Forward declared in mfat.h, refered to as the type mfat_dir_t.
188 struct mfat_dir_struct {
189 mfat_file_t* file; // File corresponding to this directory (NULL if not open).
190 mfat_dirent_t dirent; // The current dirent (as returned by readdir()).
191 mfat_cluster_pos_t cpos; // Cluster position.
192 uint32_t blocks_left; // Blocks left to read (only used for FAT16 root dirs).
193 uint32_t block_offset; // Offset relative to the block start.
194 int items_left; // HACK!!!!
197 typedef struct {
198 int state;
199 uint32_t blk_no;
200 uint8_t buf[MFAT_BLOCK_SIZE];
201 } mfat_cached_block_t;
203 typedef struct {
204 mfat_cached_block_t block[MFAT_NUM_CACHED_BLOCKS];
205 #if MFAT_NUM_CACHED_BLOCKS > 1
206 // This is a priority queue: The last item in the queue is an index to the
207 // least recently used cached block item.
208 int pri[MFAT_NUM_CACHED_BLOCKS];
209 #endif
210 } mfat_cache_t;
212 typedef struct {
213 mfat_bool_t initialized;
214 int active_partition;
215 mfat_read_block_fun_t read;
216 #if MFAT_ENABLE_WRITE
217 mfat_write_block_fun_t write;
218 #endif
219 void* custom;
220 mfat_partition_t partition[MFAT_NUM_PARTITIONS];
221 mfat_file_t file[MFAT_NUM_FDS];
222 mfat_dir_t dir[MFAT_NUM_DIRS];
223 mfat_cache_t cache[MFAT_NUM_CACHES];
224 } mfat_ctx_t;
226 // Statically allocated state.
227 static mfat_ctx_t s_ctx;
229 //--------------------------------------------------------------------------------------------------
230 // Private functions.
231 //--------------------------------------------------------------------------------------------------
233 static inline uint32_t _mfat_min(uint32_t a, uint32_t b) {
234 return (a < b) ? a : b;
237 static uint32_t _mfat_get_word(const uint8_t* buf) {
238 return ((uint32_t)buf[0]) | (((uint32_t)buf[1]) << 8);
241 static uint32_t _mfat_get_dword(const uint8_t* buf) {
242 return ((uint32_t)buf[0]) | (((uint32_t)buf[1]) << 8) | (((uint32_t)buf[2]) << 16) |
243 (((uint32_t)buf[3]) << 24);
246 static mfat_bool_t _mfat_cmpbuf(const uint8_t* a, const uint8_t* b, const uint32_t nbyte) {
247 for (uint32_t i = 0; i < nbyte; ++i) {
248 if (a[i] != b[i]) {
249 return false;
252 return true;
255 #if MFAT_ENABLE_MBR
256 static mfat_bool_t _mfat_is_fat_part_id(uint32_t id) {
257 switch (id) {
258 case MFAT_PART_ID_FAT16_LT32GB:
259 case MFAT_PART_ID_FAT16_GT32GB:
260 case MFAT_PART_ID_FAT16_GT32GB_LBA:
261 case MFAT_PART_ID_FAT32:
262 case MFAT_PART_ID_FAT32_LBA:
263 return true;
264 default:
265 return false;
268 #endif // MFAT_ENABLE_MBR
270 #if MFAT_ENABLE_GPT
271 static mfat_bool_t _mfat_is_fat_part_guid(const uint8_t* guid) {
272 // Windows Basic data partition GUID = EBD0A0A2-B9E5-4433-87C0-68B6B72699C7
273 static const uint8_t BDP_GUID[] = {0xa2,
274 0xa0,
275 0xd0,
276 0xeb,
277 0xe5,
278 0xb9,
279 0x33,
280 0x44,
281 0x87,
282 0xc0,
283 0x68,
284 0xb6,
285 0xb7,
286 0x26,
287 0x99,
288 0xc7};
289 if (_mfat_cmpbuf(guid, &BDP_GUID[0], sizeof(BDP_GUID))) {
290 // TODO(m): These may also be NTFS partitions. How to detect?
291 return true;
293 return false;
295 #endif // MFAT_ENABLE_GPT
297 static mfat_bool_t _mfat_is_valid_bpb(const uint8_t* bpb_buf) {
298 // Check that the BPB signature is there.
299 if (bpb_buf[510] != 0x55U || bpb_buf[511] != 0xaaU) {
300 DBGF("\t\tInvalid BPB signature: [%02x,%02x]", bpb_buf[510], bpb_buf[511]);
301 return false;
304 // Check for a valid jump instruction (first three bytes).
305 if (bpb_buf[0] != 0xe9 && !(bpb_buf[0] == 0xeb && bpb_buf[2] == 0x90)) {
306 DBGF("\t\tInvalid BPB jump code: [%02x,%02x,%02x]", bpb_buf[0], bpb_buf[1], bpb_buf[2]);
307 return false;
310 // Check for valid bytes per sector.
311 uint32_t bps = _mfat_get_word(&bpb_buf[11]);
312 if (bps != 512 && bps != 1024 && bps != 2048 && bps != 4096) {
313 DBGF("\t\tInvalid BPB bytes per sector: %" PRIu32, bps);
314 return false;
317 return true;
320 static mfat_bool_t _mfat_is_valid_shortname_file(const uint8_t* dir_entry) {
321 const uint8_t attr = dir_entry[11];
322 if (((attr & MFAT_ATTR_LONG_NAME) == MFAT_ATTR_LONG_NAME) ||
323 ((attr & MFAT_ATTR_VOLUME_ID) == MFAT_ATTR_VOLUME_ID)) {
324 return false;
326 return true;
329 static mfat_cached_block_t* _mfat_get_cached_block(uint32_t blk_no, int cache_type) {
330 // Pick the relevant cache.
331 mfat_cache_t* cache = &s_ctx.cache[cache_type];
333 #if MFAT_NUM_CACHED_BLOCKS > 1
334 // By default, pick the last (least recently used) item in the pirority queue...
335 int item_id = cache->pri[MFAT_NUM_CACHED_BLOCKS - 1];
337 // ...but override it if we have a cache hit.
338 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
339 mfat_cached_block_t* cb = &cache->block[i];
340 if (cb->state != MFAT_INVALID && cb->blk_no == blk_no) {
341 item_id = i;
342 break;
346 // Move the selected item to the front of the priority queue (i.e. it's the most recently used
347 // cache item).
349 int prev_id = item_id;
350 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
351 int this_id = cache->pri[i];
352 cache->pri[i] = prev_id;
353 if (this_id == item_id) {
354 break;
356 prev_id = this_id;
360 mfat_cached_block_t* cached_block = &cache->block[item_id];
361 #else
362 mfat_cached_block_t* cached_block = &cache->block[0];
363 #endif
365 // Reassign the cached block to the requested block number (if necessary).
366 if (cached_block->blk_no != blk_no) {
367 #if MFAT_ENABLE_DEBUG
368 if (cached_block->state != MFAT_INVALID) {
369 DBGF("Cache %d: Evicting block %" PRIu32 " in favor of block %" PRIu32,
370 cache_type,
371 cached_block->blk_no,
372 blk_no);
374 #endif
376 #if MFAT_ENABLE_WRITE
377 // Flush the block?
378 if (cached_block->state == MFAT_DIRTY) {
379 DBGF("Cache %d: Flushing evicted block %" PRIu32, cache_type, cached_block->blk_no);
380 if (s_ctx.write((const char*)cached_block->buf, cached_block->blk_no, s_ctx.custom) == -1) {
381 // FATAL: We can't recover from here... :-(
382 DBGF("Cache %d: Failed to flush the block", cache_type);
383 return NULL;
386 #endif
388 // Set the new block ID.
389 cached_block->blk_no = blk_no;
391 // The contents of the buffer is now invalid.
392 cached_block->state = MFAT_INVALID;
395 return cached_block;
398 static mfat_cached_block_t* _mfat_read_block(uint32_t block_no, int cache_type) {
399 // First query the cache.
400 mfat_cached_block_t* block = _mfat_get_cached_block(block_no, cache_type);
401 if (block == NULL) {
402 return NULL;
405 // If necessary, read the block from storage.
406 if (block->state == MFAT_INVALID) {
407 if (s_ctx.read((char*)block->buf, block_no, s_ctx.custom) == -1) {
408 return NULL;
410 block->state = MFAT_VALID;
413 return block;
416 // Helper function for finding the next cluster in a cluster chain.
417 static mfat_bool_t _mfat_next_cluster(const mfat_partition_t* part, uint32_t* cluster) {
418 const uint32_t fat_entry_size = (part->type == MFAT_PART_TYPE_FAT32) ? 4 : 2;
420 uint32_t fat_offset = fat_entry_size * (*cluster);
421 uint32_t fat_block =
422 part->first_block + part->num_reserved_blocks + (fat_offset / MFAT_BLOCK_SIZE);
423 uint32_t fat_block_offset = fat_offset % MFAT_BLOCK_SIZE;
425 // For FAT copy no. N (0..num_fats-1):
426 // fat_block += N * part->blocks_per_fat
428 // Read the FAT block into a cached buffer.
429 mfat_cached_block_t* block = _mfat_read_block(fat_block, MFAT_CACHE_FAT);
430 if (block == NULL) {
431 DBGF("Failed to read the FAT block %" PRIu32, fat_block);
432 return false;
434 uint8_t* buf = &block->buf[0];
436 // Get the value for this cluster from the FAT.
437 uint32_t next_cluster;
438 if (part->type == MFAT_PART_TYPE_FAT32) {
439 // For FAT32 we mask off upper 4 bits, as the cluster number is 28 bits.
440 next_cluster = _mfat_get_dword(&buf[fat_block_offset]) & 0x0fffffffU;
441 } else {
442 next_cluster = _mfat_get_word(&buf[fat_block_offset]);
443 if (next_cluster >= 0xfff7U) {
444 // Convert FAT16 special codes (BAD & EOC) to FAT32 codes.
445 next_cluster |= 0x0fff0000U;
449 // This is a sanity check (failure indicates a corrupt filesystem). We should really do this check
450 // BEFORE accessing the cluster instead.
451 // We do not expect to see:
452 // 0x00000000 - free cluster
453 // 0x0ffffff7 - BAD cluster
454 if (next_cluster == 0U || next_cluster == 0x0ffffff7U) {
455 DBGF("Unexpected next cluster: 0x%08" PRIx32, next_cluster);
456 return false;
459 DBGF("Next cluster: %" PRIu32 " (0x%08" PRIx32 ")", next_cluster, next_cluster);
461 // Return the next cluster number.
462 *cluster = next_cluster;
464 return true;
467 // Helper function for finding the first block of a cluster.
468 static uint32_t _mfat_first_block_of_cluster(const mfat_partition_t* part, uint32_t cluster) {
469 return part->first_data_block + ((cluster - 2U) * part->blocks_per_cluster);
472 // Initialize a cluster pos object to the current cluster & offset (e.g. for a file).
473 static mfat_cluster_pos_t _mfat_cluster_pos_init(const mfat_partition_t* part,
474 const uint32_t cluster_no,
475 const uint32_t offset) {
476 mfat_cluster_pos_t cpos;
477 cpos.cluster_no = cluster_no;
478 cpos.block_in_cluster = (offset % (part->blocks_per_cluster * MFAT_BLOCK_SIZE)) / MFAT_BLOCK_SIZE;
479 cpos.cluster_start_blk = _mfat_first_block_of_cluster(part, cluster_no);
480 return cpos;
483 // Initialize a cluster pos object to the current file offset of the specified file.
484 static mfat_cluster_pos_t _mfat_cluster_pos_init_from_file(const mfat_partition_t* part,
485 const mfat_file_t* f) {
486 return _mfat_cluster_pos_init(part, f->current_cluster, f->offset);
489 // Advance a cluster pos by one block.
490 static mfat_bool_t _mfat_cluster_pos_advance(mfat_cluster_pos_t* cpos,
491 const mfat_partition_t* part) {
492 ++cpos->block_in_cluster;
493 if (cpos->block_in_cluster == part->blocks_per_cluster) {
494 if (!_mfat_next_cluster(part, &cpos->cluster_no)) {
495 return false;
497 cpos->cluster_start_blk = _mfat_first_block_of_cluster(part, cpos->cluster_no);
498 cpos->block_in_cluster = 0;
500 return true;
503 // Get the current absolute block of a cluster pos object.
504 static uint32_t _mfat_cluster_pos_blk_no(const mfat_cluster_pos_t* cpos) {
505 return cpos->cluster_start_blk + cpos->block_in_cluster;
508 // Check for EOC (End of clusterchain). NOTE: EOC cluster is included in file.
509 static mfat_bool_t _mfat_is_eoc(const uint32_t cluster) {
510 return cluster >= 0x0ffffff8U;
513 #if MFAT_ENABLE_GPT
514 static mfat_bool_t _mfat_decode_gpt(void) {
515 // Read the primary GUID Partition Table (GPT) header, located at block 1.
516 mfat_cached_block_t* block = _mfat_read_block(1U, MFAT_CACHE_DATA);
517 if (block == NULL) {
518 DBG("Failed to read the GPT");
519 return false;
521 uint8_t* buf = &block->buf[0];
523 // Is this in fact an GPT header?
524 // TODO(m): We could do more validation (CRC etc).
525 static const uint8_t gpt_sig[] = {0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54};
526 if (!_mfat_cmpbuf(&buf[0], &gpt_sig[0], sizeof(gpt_sig))) {
527 DBG("Not a valid GPT signature");
528 return false;
531 // Get the start LBA of the the partition entries.
532 // Note: This is actually a 64-bit value, but we don't support such large media anyway, and by
533 // reading it out as a 32-bit little endian number we get the correct value IF the upper 32
534 // bits are zero. This should be OK as the value should always be 0x0000000000000002 for the
535 // primary copy, which we are interested in.
536 uint32_t entries_block = _mfat_get_dword(&buf[72]);
538 // Get the number of partition entries.
539 uint32_t num_entries = _mfat_get_dword(&buf[80]);
541 // Get the size of each partition entry, in bytes.
542 uint32_t entry_size = _mfat_get_dword(&buf[84]);
544 DBGF("GPT header: entries_block=%" PRIu32 ", num_entries=%" PRIu32 ", entry_size=%" PRIu32,
545 entries_block,
546 num_entries,
547 entry_size);
549 uint32_t entry_offs = 0;
550 for (uint32_t i = 0; i < num_entries && i < MFAT_NUM_PARTITIONS; ++i) {
551 // Read the next block of the partition entry array if necessary.
552 if ((entry_offs % MFAT_BLOCK_SIZE) == 0) {
553 block = _mfat_read_block(entries_block, MFAT_CACHE_DATA);
554 if (block == NULL) {
555 DBGF("Failed to read the GPT partition entry array at block %" PRIu32, entries_block);
556 return false;
558 buf = &block->buf[0];
560 ++entries_block;
561 entry_offs = 0;
564 // Decode the partition entry.
565 uint8_t* entry = &buf[entry_offs];
566 mfat_partition_t* part = &s_ctx.partition[i];
568 // Get the starting block of the partition (again, 64-bit value that we read as a 32-bit
569 // value).
570 part->first_block = _mfat_get_dword(&entry[32]);
572 // Check if the partition is bootable (bit ).
573 part->boot = ((entry[48] & 0x04) != 0);
575 // Is this a potential FAT partition?
576 if (_mfat_is_fat_part_guid(&entry[0])) {
577 // The actual FAT type is determined later.
578 part->type = MFAT_PART_TYPE_FAT_UNDECIDED;
581 // Note: We print GUIDs in byte order, as we expect them in our signatures, NOT in GUID
582 // mixed endian order.
583 DBGF("GPT entry %" PRIu32 ": first_block = %" PRIu32 ", GUID = %08" PRIx32 "%08" PRIx32
584 "%08" PRIx32 "%08" PRIx32 ", FAT = %s",
586 part->first_block,
587 _mfat_get_dword(&entry[12]),
588 _mfat_get_dword(&entry[8]),
589 _mfat_get_dword(&entry[4]),
590 _mfat_get_dword(&entry[0]),
591 part->type != MFAT_PART_TYPE_UNKNOWN ? "Yes" : "No");
593 entry_offs += entry_size;
596 return true;
598 #endif // MFAT_ENABLE_GPT
600 #if MFAT_ENABLE_MBR
601 static mfat_bool_t _mfat_decode_mbr(void) {
602 mfat_cached_block_t* block = _mfat_read_block(0U, MFAT_CACHE_DATA);
603 if (block == NULL) {
604 DBG("Failed to read the MBR");
605 return false;
607 uint8_t* buf = &block->buf[0];
609 // Is this an MBR block (can also be a partition boot record)?
610 mfat_bool_t found_valid_mbr = false;
611 if (buf[510] == 0x55U && buf[511] == 0xaaU) {
612 // Parse each partition entry.
613 for (int i = 0; i < 4 && i < MFAT_NUM_PARTITIONS; ++i) {
614 uint8_t* entry = &buf[446U + 16U * (uint32_t)i];
615 mfat_partition_t* part = &s_ctx.partition[i];
617 // Current state of partition (00h=Inactive, 80h=Active).
618 part->boot = ((entry[0] & 0x80U) != 0);
620 // Is this a FAT partition.
621 if (_mfat_is_fat_part_id(entry[4])) {
622 // The actual FAT type is determined later.
623 part->type = MFAT_PART_TYPE_FAT_UNDECIDED;
624 found_valid_mbr = true;
627 // Get the beginning of the partition (LBA).
628 part->first_block = _mfat_get_dword(&entry[8]);
630 } else {
631 DBGF("Not a valid MBR signature: [%02x,%02x]", buf[510], buf[511]);
634 return found_valid_mbr;
636 #endif // MFAT_ENABLE_MBR
638 static void _mfat_decode_tableless(void) {
639 // Some storage media are formatted without an MBR or GPT. If so, there is only a single volume
640 // and the first block is the BPB (BIOS Parameter Block) of that "partition". We initially guess
641 // that this is the case, and let the partition decoding logic assert if this was a good guess.
642 DBG("Assuming that the storage medium does not have a partition table");
644 // Clear all partitions (their values are potentially garbage).
645 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
646 memset(&s_ctx.partition[i], 0, sizeof(mfat_partition_t));
649 // Guess that the first partition is FAT (we'll detect the actual type later).
650 // Note: The "first_block" field is cleared to zero in the previous memset, so the first
651 // partition starts at the first block, which is what we intend.
652 s_ctx.partition[0].type = MFAT_PART_TYPE_FAT_UNDECIDED;
655 static mfat_bool_t _mfat_decode_partition_tables(void) {
656 mfat_bool_t found_partition_table = false;
658 #if MFAT_ENABLE_GPT
659 // 1: Try to read the GUID Partition Table (GPT).
660 if (!found_partition_table) {
661 found_partition_table = _mfat_decode_gpt();
663 #endif
665 #if MFAT_ENABLE_MBR
666 // 2: Try to read the Master Boot Record (MBR).
667 if (!found_partition_table) {
668 found_partition_table = _mfat_decode_mbr();
670 #endif
672 // 3: Assume that the storage medium does not have a partition table at all.
673 if (!found_partition_table) {
674 _mfat_decode_tableless();
677 // Read and parse the BPB for each FAT partition.
678 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
679 DBGF("Partition %d:", i);
680 mfat_partition_t* part = &s_ctx.partition[i];
682 // Skip unsupported partition types.
683 if (part->type == MFAT_PART_TYPE_UNKNOWN) {
684 DBG("\t\tNot a FAT partition");
685 continue;
688 // Load the BPB (the first block of the partition).
689 mfat_cached_block_t* block = _mfat_read_block(part->first_block, MFAT_CACHE_DATA);
690 if (block == NULL) {
691 DBG("\t\tFailed to read the BPB");
692 return false;
694 uint8_t* buf = &block->buf[0];
696 if (!_mfat_is_valid_bpb(buf)) {
697 DBG("\t\tPartition does not appear to have a valid BPB");
698 part->type = MFAT_PART_TYPE_UNKNOWN;
699 continue;
702 // Decode useful metrics from the BPB (these are used in block number arithmetic etc).
703 uint32_t bytes_per_block = _mfat_get_word(&buf[11]);
704 part->blocks_per_cluster = buf[13];
705 part->num_reserved_blocks = _mfat_get_word(&buf[14]);
706 part->num_fats = buf[16];
708 uint32_t tot_sectors_16 = _mfat_get_word(&buf[19]);
709 uint32_t tot_sectors_32 = _mfat_get_dword(&buf[32]);
710 part->num_blocks = (tot_sectors_16 != 0) ? tot_sectors_16 : tot_sectors_32;
713 uint32_t blocks_per_fat_16 = _mfat_get_word(&buf[22]);
714 uint32_t blocks_per_fat_32 = _mfat_get_dword(&buf[36]);
715 part->blocks_per_fat = (blocks_per_fat_16 != 0) ? blocks_per_fat_16 : blocks_per_fat_32;
718 uint32_t num_root_entries = _mfat_get_word(&buf[17]);
719 part->blocks_in_root_dir =
720 ((num_root_entries * 32U) + (MFAT_BLOCK_SIZE - 1U)) / MFAT_BLOCK_SIZE;
723 // Derive useful metrics.
724 part->first_data_block = part->first_block + part->num_reserved_blocks +
725 (part->num_fats * part->blocks_per_fat) + part->blocks_in_root_dir;
727 // Check that the number of bytes per sector is 512.
728 // TODO(m): We could add support for larger block sizes.
729 if (bytes_per_block != MFAT_BLOCK_SIZE) {
730 DBGF("\t\tUnsupported block size: %" PRIu32, bytes_per_block);
731 part->type = MFAT_PART_TYPE_UNKNOWN;
732 continue;
735 // Up until now we have only been guessing the partition FAT type. Now we determine the *actual*
736 // partition type (FAT12, FAT16 or FAT32), using the detection algorithm given in the FAT32 File
737 // System Specification, p.14. The definition is based on the count of clusters as follows:
738 // FAT12: count of clusters < 4085
739 // FAT16: 4085 <= count of clusters < 65525
740 // FAT32: 65525 <= count of clusters
742 uint32_t root_ent_cnt = _mfat_get_word(&buf[17]);
743 uint32_t root_dir_sectors = ((root_ent_cnt * 32) + (MFAT_BLOCK_SIZE - 1)) / MFAT_BLOCK_SIZE;
745 uint32_t data_sectors =
746 part->num_blocks -
747 (part->num_reserved_blocks + (part->num_fats * part->blocks_per_fat) + root_dir_sectors);
749 uint32_t count_of_clusters = data_sectors / part->blocks_per_cluster;
751 #if MFAT_ENABLE_WRITE
752 // We need to know the actual number of clusters when writing files.
753 part->num_clusters = count_of_clusters + 1;
754 #endif
756 // We don't support FAT12.
757 if (count_of_clusters < 4085) {
758 part->type = MFAT_PART_TYPE_UNKNOWN;
759 DBG("\t\tFAT12 is not supported.");
760 continue;
763 if (count_of_clusters < 65525) {
764 part->type = MFAT_PART_TYPE_FAT16;
765 } else {
766 part->type = MFAT_PART_TYPE_FAT32;
770 // Determine the starting block/cluster for the root directory.
771 if (part->type == MFAT_PART_TYPE_FAT16) {
772 part->root_dir_block = part->first_data_block - part->blocks_in_root_dir;
773 } else {
774 part->root_dir_cluster = _mfat_get_dword(&buf[44]);
777 #if MFAT_ENABLE_DEBUG
778 // Print the partition information.
779 DBGF("\t\ttype = %s", part->type == MFAT_PART_TYPE_FAT16 ? "FAT16" : "FAT32");
780 DBGF("\t\tboot = %s", part->boot ? "Yes" : "No");
781 DBGF("\t\tfirst_block = %" PRIu32, part->first_block);
782 DBGF("\t\tbytes_per_block = %" PRIu32, bytes_per_block);
783 DBGF("\t\tnum_blocks = %" PRIu32, part->num_blocks);
784 DBGF("\t\tblocks_per_cluster = %" PRIu32, part->blocks_per_cluster);
785 #if MFAT_ENABLE_WRITE
786 DBGF("\t\tnum_clusters = %" PRIu32, part->num_clusters);
787 #endif
788 DBGF("\t\tblocks_per_fat = %" PRIu32, part->blocks_per_fat);
789 DBGF("\t\tnum_fats = %" PRIu32, part->num_fats);
790 DBGF("\t\tnum_reserved_blocks = %" PRIu32, part->num_reserved_blocks);
791 DBGF("\t\troot_dir_block = %" PRIu32, part->root_dir_block);
792 DBGF("\t\troot_dir_cluster = %" PRIu32, part->root_dir_cluster);
793 DBGF("\t\tblocks_in_root_dir = %" PRIu32, part->blocks_in_root_dir);
794 DBGF("\t\tfirst_data_block = %" PRIu32, part->first_data_block);
796 // Print the extended boot signature.
797 const uint8_t* ex_boot_sig = (part->type == MFAT_PART_TYPE_FAT32) ? &buf[66] : &buf[38];
798 if (ex_boot_sig[0] == 0x29) {
799 uint32_t vol_id = _mfat_get_dword(&ex_boot_sig[1]);
801 char label[12];
802 memcpy(&label[0], &ex_boot_sig[5], 11);
803 label[11] = 0;
805 char fs_type[9];
806 memcpy(&fs_type[0], &ex_boot_sig[16], 8);
807 fs_type[8] = 0;
809 DBGF("\t\tvol_id = %04" PRIx32 ":%04" PRIx32, (vol_id >> 16), vol_id & 0xffffU);
810 DBGF("\t\tlabel = \"%s\"", label);
811 DBGF("\t\tfs_type = \"%s\"", fs_type);
812 } else {
813 DBGF("\t\t(Boot signature N/A - bpb[%d]=0x%02x)", (int)(ex_boot_sig - buf), ex_boot_sig[0]);
815 #endif // MFAT_ENABLE_DEBUG
818 return true;
821 static mfat_file_t* _mfat_fd_to_file(int fd) {
822 if (fd < 0 || fd >= MFAT_NUM_FDS) {
823 DBGF("FD out of range: %d", fd);
824 return NULL;
826 mfat_file_t* f = &s_ctx.file[fd];
827 if (!f->open) {
828 DBGF("File is not open: %d", fd);
829 return NULL;
831 return f;
834 static void _mfat_dir_entry_to_stat(uint8_t* dir_entry, mfat_stat_t* stat) {
835 // Decode file attributes.
836 uint32_t attr = dir_entry[11];
837 uint32_t st_mode =
838 MFAT_S_IRUSR | MFAT_S_IRGRP | MFAT_S_IROTH | MFAT_S_IXUSR | MFAT_S_IXGRP | MFAT_S_IXOTH;
839 if ((attr & MFAT_ATTR_READ_ONLY) == 0) {
840 st_mode |= MFAT_S_IWUSR | MFAT_S_IWGRP | MFAT_S_IWOTH;
842 if ((attr & MFAT_ATTR_DIRECTORY) != 0) {
843 st_mode |= MFAT_S_IFDIR;
844 } else {
845 st_mode |= MFAT_S_IFREG;
847 stat->st_mode = st_mode;
849 // Decode time.
850 uint32_t time = _mfat_get_word(&dir_entry[22]);
851 stat->st_mtim.hour = time >> 11;
852 stat->st_mtim.minute = (time >> 5) & 63;
853 stat->st_mtim.second = (time & 31) * 2;
855 // Decode date.
856 uint32_t date = _mfat_get_word(&dir_entry[24]);
857 stat->st_mtim.year = (date >> 9) + 1980U;
858 stat->st_mtim.month = (date >> 5) & 15;
859 stat->st_mtim.day = date & 31;
861 // Get file size.
862 stat->st_size = _mfat_get_dword(&dir_entry[28]);
865 static int _mfat_canonicalize_char(int c) {
866 // Valid character?
867 if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '$' || c == '%' || c == '-' ||
868 c == '_' || c == '@' || c == '~' || c == '`' || c == '!' || c == '(' || c == ')' ||
869 c == '{' || c == '}' || c == '^' || c == '#' || c == '&') {
870 return c;
873 // Convert lower case to upper case.
874 if (c >= 'a' && c <= 'z') {
875 return c - 'a' + 'A';
878 // Invalid character.
879 return '!';
882 /// @brief Canonicalize a file name.
884 /// Convert the next path part (up until a directory separator or the end of the string) into a
885 /// valid 8.3 short file name as represented in a FAT directory entry (i.e. space padded and without
886 /// the dot). File names that do not fit in an 8.3 representation will be silently truncated.
888 /// Examples:
889 /// - "hello.txt" -> "HELLO TXT"
890 /// - "File.1" -> "FILE 1 "
891 /// - "ALongFileName.json" -> "ALONGFILJSO"
892 /// - "bin/foo.exe" -> "BIN " (followed by "FOO EXE" at the next invocation)
893 /// - "." -> ". "
894 /// - ".." -> ".. "
895 /// @param path A path to a file, possibly includuing directory separators (/ or \).
896 /// @param[out] fname The canonicalized file name.
897 /// @returns the index of the next path part, or -1 if this was the last path part.
898 static int _mfat_canonicalize_fname(const char* path, char name[12]) {
899 int pos = 0;
900 int npos = 0;
901 int c;
903 // Special cases: "." and ".." (unlike other file names, the dots are spelled out in these names).
904 if (path[0] == '.' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) {
905 name[0] = '.';
906 for (int i = 1; i < 11; ++i) {
907 name[i] = ' ';
909 name[11] = 0;
910 return (path[1] == 0) ? -1 : 2;
912 if (path[0] == '.' && path[1] == '.' && (path[2] == 0 || path[2] == '/' || path[2] == '\\')) {
913 name[0] = '.';
914 name[1] = '.';
915 for (int i = 2; i < 11; ++i) {
916 name[i] = ' ';
918 name[11] = 0;
919 return (path[2] == 0) ? -1 : 3;
922 // Extract the name part.
923 while (true) {
924 c = (int)(uint8_t)path[pos++];
925 if (c == 0 || c == '.' || c == '/' || c == '\\') {
926 break;
928 if (npos < 8) {
929 name[npos++] = (char)_mfat_canonicalize_char(c);
933 // Space-fill remaining characters of the name part.
934 for (; npos < 8; ++npos) {
935 name[npos] = ' ';
938 // Extract the extension part.
939 if (c == '.') {
940 while (true) {
941 c = (int)(uint8_t)path[pos++];
942 if (c == 0 || c == '/' || c == '\\') {
943 break;
945 if (npos < 11) {
946 name[npos++] = (char)_mfat_canonicalize_char(c);
951 // Space-fill remaining characters of the extension part.
952 for (; npos < 11; ++npos) {
953 name[npos] = ' ';
956 // Zero terminate the string.
957 name[11] = 0;
959 // Was this a directory part of the path (ignore trailing directory separators)?
960 if ((c == '/' || c == '\\') && path[pos] != 0) {
961 // Return the starting position of the next path part.
962 return pos;
965 // Indicate that there are no more path parts by returning -1.
966 return -1;
969 #if MFAT_ENABLE_OPENDIR
970 static void _mfat_make_printable_fname(const uint8_t* canonical_name, char printable_name[13]) {
971 int src_idx = 0;
972 int dst_idx = 0;
974 // Name part.
975 while (src_idx < 8 && canonical_name[src_idx] != ' ') {
976 printable_name[dst_idx++] = canonical_name[src_idx++];
978 src_idx = 8;
980 // Extension part.
981 while (src_idx < 11 && canonical_name[src_idx] != ' ') {
982 if (src_idx == 8) {
983 printable_name[dst_idx++] = '.';
985 printable_name[dst_idx++] = canonical_name[src_idx++];
988 // Zero-terminate.
989 printable_name[dst_idx] = 0;
991 #endif
993 /// @brief Find a file on the given partition.
995 /// If the directory (if part of the path) exists, but the file does not exist in the directory,
996 /// this function will find the first free directory entry slot and set @c exists to false. This is
997 /// useful for creating new files.
998 /// @param part_no The partition number.
999 /// @param path The absolute path to the file.
1000 /// @param[out] info Information about the file.
1001 /// @param[out] file_type The file type (e.g. dir or regular file).
1002 /// @param[out] exists true if the file exists, false if it needs to be created.
1003 /// @returns true if the file (or its potential slot) was found.
1004 static mfat_bool_t _mfat_find_file(int part_no,
1005 const char* path,
1006 mfat_file_info_t* info,
1007 int* file_type,
1008 mfat_bool_t* exists) {
1009 mfat_partition_t* part = &s_ctx.partition[part_no];
1011 // Start with the root directory cluster/block.
1012 mfat_cluster_pos_t cpos;
1013 uint32_t blocks_left;
1014 if (part->type == MFAT_PART_TYPE_FAT32) {
1015 cpos = _mfat_cluster_pos_init(part, part->root_dir_cluster, 0);
1016 blocks_left = 0xffffffffU;
1017 } else {
1018 // We use a fake/tweaked cluster pos for FAT16 root directories.
1019 cpos.cluster_no = 0U;
1020 cpos.cluster_start_blk = part->root_dir_block;
1021 cpos.block_in_cluster = 0U;
1022 blocks_left = part->blocks_in_root_dir;
1025 // Special case: Is the caller trying to open the root directory?
1026 mfat_bool_t is_root_dir = false;
1027 if (path[1] == 0 && (path[0] == '/' || path[0] == '\\')) {
1028 DBG("Request to open root directory");
1029 is_root_dir = true;
1032 // Try to find the given path.
1033 mfat_cached_block_t* block = NULL;
1034 uint8_t* file_entry = NULL;
1035 if (!is_root_dir) {
1036 // Skip leading slashes.
1037 while (*path == '/' || *path == '\\') {
1038 ++path;
1041 int path_pos = 0;
1042 while (path_pos >= 0) {
1043 // Extract a directory entry compatible file name.
1044 char fname[12];
1045 int name_pos = _mfat_canonicalize_fname(&path[path_pos], fname);
1046 mfat_bool_t is_parent_dir = (name_pos >= 0);
1048 path_pos = is_parent_dir ? path_pos + name_pos : -1;
1049 DBGF("Looking for %s: \"%s\"", is_parent_dir ? "parent dir" : "file", fname);
1051 // Use an "unlimited" block counter if we're doing a clusterchain lookup.
1052 if (cpos.cluster_no != 0U) {
1053 blocks_left = 0xffffffffU;
1056 // Look up the file name in the directory.
1057 mfat_bool_t no_more_entries = false;
1058 for (; file_entry == NULL && !no_more_entries && blocks_left > 0U; --blocks_left) {
1059 // Load the directory table block.
1060 block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1061 if (block == NULL) {
1062 DBGF("Unable to load directory block %" PRIu32, _mfat_cluster_pos_blk_no(&cpos));
1063 return false;
1065 uint8_t* buf = &block->buf[0];
1067 // Loop over all the files in this directory block.
1068 uint8_t* found_entry = NULL;
1069 for (uint32_t offs = 0U; offs < 512U; offs += 32U) {
1070 uint8_t* entry = &buf[offs];
1072 // TODO(m): Look for the first 0xe5-entry too in case we want to create a new file.
1074 // Last entry in the directory structure?
1075 if (entry[0] == 0x00) {
1076 no_more_entries = true;
1077 break;
1080 // Not a file/dir entry?
1081 if (!_mfat_is_valid_shortname_file(entry)) {
1082 continue;
1085 // Is this the file/dir that we are looking for?
1086 if (_mfat_cmpbuf(&entry[0], (const uint8_t*)&fname[0], 11)) {
1087 found_entry = entry;
1088 break;
1092 // Did we have a match.
1093 if (found_entry != NULL) {
1094 uint32_t attr = found_entry[11];
1096 // Descend into directory?
1097 if (is_parent_dir) {
1098 if ((attr & MFAT_ATTR_DIRECTORY) == 0U) {
1099 DBGF("Not a directory: %s", fname);
1100 return false;
1103 // Decode the starting cluster of the child directory entry table.
1104 uint32_t chile_dir_cluster_no =
1105 (_mfat_get_word(&found_entry[20]) << 16) | _mfat_get_word(&found_entry[26]);
1106 cpos = _mfat_cluster_pos_init(part, chile_dir_cluster_no, 0);
1107 blocks_left = 0xffffffffU;
1108 } else {
1109 file_entry = found_entry;
1112 break;
1115 // Go to the next block in the directory.
1116 if (cpos.cluster_no != 0U) {
1117 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1118 return false;
1120 } else {
1121 cpos.block_in_cluster += 1; // FAT16 style linear block access.
1125 // Break loop if we didn't find the file.
1126 if (no_more_entries) {
1127 break;
1132 // Could we neither find the file nor a new directory slot for the file?
1133 if (file_entry == NULL && !is_root_dir) {
1134 return false;
1137 // Define the file properties.
1138 info->part_no = part_no;
1139 if (is_root_dir) {
1140 // Special handling of the root directory (it does not have an entry in any directory block).
1141 info->size = 0U;
1142 info->first_cluster = cpos.cluster_no;
1143 info->dir_entry_block = 0U;
1144 info->dir_entry_offset = 0U;
1145 *file_type = (cpos.cluster_no == 0U) ? MFAT_FILE_TYPE_FAT16ROOTDIR : MFAT_FILE_TYPE_DIR;
1146 *exists = true;
1147 } else {
1148 // For files and non root dirs, we extract file information from the directory block.
1149 info->size = _mfat_get_dword(&file_entry[28]);
1150 info->first_cluster = (_mfat_get_word(&file_entry[20]) << 16) | _mfat_get_word(&file_entry[26]);
1151 info->dir_entry_block = block->blk_no;
1152 info->dir_entry_offset = file_entry - block->buf;
1154 // Does the file exist?
1155 if ((file_entry[0] != 0x00) && (file_entry[0] != 0xe5)) {
1156 *file_type = (file_entry[11] & MFAT_ATTR_DIRECTORY) != 0U ? MFAT_FILE_TYPE_DIR
1157 : MFAT_FILE_TYPE_REGULAR;
1158 *exists = true;
1159 } else {
1160 // Non-existent files must be regular files, since we can't create directories with
1161 // MFAT_O_CREAT.
1162 *file_type = MFAT_FILE_TYPE_REGULAR;
1163 *exists = false;
1167 return true;
1170 #if MFAT_ENABLE_WRITE
1171 static void _mfat_sync_impl() {
1172 for (int j = 0; j < MFAT_NUM_CACHES; ++j) {
1173 mfat_cache_t* cache = &s_ctx.cache[j];
1174 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
1175 mfat_cached_block_t* cb = &cache->block[i];
1176 if (cb->state == MFAT_DIRTY) {
1177 DBGF("Cache: Flushing block %" PRIu32, cb->blk_no);
1178 s_ctx.write((const char*)cb->buf, cb->blk_no, s_ctx.custom);
1179 cb->state = MFAT_VALID;
1184 #endif
1186 static int _mfat_fstat_impl(mfat_file_info_t* info, mfat_stat_t* stat) {
1187 // We can't stat root directories.
1188 if (info->dir_entry_block == 0U) {
1189 return -1;
1192 // Read the directory entry block (should already be in the cache).
1193 mfat_cached_block_t* block = _mfat_read_block(info->dir_entry_block, MFAT_CACHE_DATA);
1194 if (block == NULL) {
1195 return -1;
1198 // Extract the relevant information for this directory entry.
1199 uint8_t* dir_entry = &block->buf[info->dir_entry_offset];
1200 _mfat_dir_entry_to_stat(dir_entry, stat);
1202 return 0;
1205 static int _mfat_stat_impl(const char* path, mfat_stat_t* stat) {
1206 // Find the file in the file system structure.
1207 mfat_bool_t is_dir;
1208 mfat_bool_t exists;
1209 mfat_file_info_t info;
1210 mfat_bool_t ok = _mfat_find_file(s_ctx.active_partition, path, &info, &is_dir, &exists);
1211 if (!ok || !exists) {
1212 DBGF("File not found: %s", path);
1213 return -1;
1216 return _mfat_fstat_impl(&info, stat);
1219 static int _mfat_open_impl(const char* path, int oflag) {
1220 // Find the next free fd.
1221 int fd;
1222 for (fd = 0; fd < MFAT_NUM_FDS; ++fd) {
1223 if (!s_ctx.file[fd].open) {
1224 break;
1227 if (fd >= MFAT_NUM_FDS) {
1228 DBG("No free FD:s left");
1229 return -1;
1231 mfat_file_t* f = &s_ctx.file[fd];
1233 // Find the file in the file system structure.
1234 int file_type;
1235 mfat_bool_t exists;
1236 if (!_mfat_find_file(s_ctx.active_partition, path, &f->info, &file_type, &exists)) {
1237 DBGF("File not found: %s", path);
1238 return -1;
1240 mfat_bool_t is_dir =
1241 (file_type == MFAT_FILE_TYPE_DIR) || (file_type == MFAT_FILE_TYPE_FAT16ROOTDIR);
1243 // Check that we found the correct type.
1244 if (((oflag & MFAT_O_DIRECTORY) != 0) && !is_dir) {
1245 DBGF("Can not open the file as a directory: %s", path);
1246 return -1;
1249 // Handle non-existing files (can only happen for regular files).
1250 if (!exists) {
1251 #if MFAT_ENABLE_WRITE
1252 // Should we create the file?
1253 if ((oflag & MFAT_O_CREAT) != 0U) {
1254 DBG("Creating files is not yet implemented");
1255 return -1;
1257 #endif
1259 DBGF("File does not exist: %s", path);
1260 return -1;
1263 // Initialize the file state.
1264 f->open = true;
1265 f->type = file_type;
1266 f->oflag = oflag;
1267 f->current_cluster = f->info.first_cluster;
1268 f->offset = 0U;
1270 DBGF("Opening file: first_cluster = %" PRIu32 " (block = %" PRIu32 "), size = %" PRIu32
1271 " bytes, dir_blk = %" PRIu32
1272 ", dir_offs "
1273 "= %" PRIu32,
1274 f->info.first_cluster,
1275 _mfat_first_block_of_cluster(&s_ctx.partition[f->info.part_no], f->info.first_cluster),
1276 f->info.size,
1277 f->info.dir_entry_block,
1278 f->info.dir_entry_offset);
1280 return fd;
1283 static int _mfat_close_impl(mfat_file_t* f) {
1284 #if MFAT_ENABLE_WRITE
1285 // For good measure, we flush pending writes when a file is closed (only do this when closing
1286 // files that are open with write permissions).
1287 if ((f->oflag & MFAT_O_WRONLY) != 0) {
1288 _mfat_sync_impl();
1290 #endif
1292 // The file is no longer open. This makes the fd available for future open() requests.
1293 f->open = false;
1295 return 0;
1298 static int64_t _mfat_read_impl(mfat_file_t* f, uint8_t* buf, uint32_t nbyte) {
1299 // Is the file open with read permissions?
1300 if ((f->oflag & MFAT_O_RDONLY) == 0) {
1301 return -1;
1304 // Determine actual size of the operation (clamp to the size of the file).
1305 if (nbyte > (f->info.size - f->offset)) {
1306 nbyte = f->info.size - f->offset;
1307 DBGF("read: Clamped read request to %" PRIu32 " bytes", nbyte);
1310 // Early out if only zero bytes were requested (e.g. if we are at the EOF).
1311 if (nbyte == 0U) {
1312 return 0;
1315 // Start out at the current file offset.
1316 mfat_partition_t* part = &s_ctx.partition[f->info.part_no];
1317 mfat_cluster_pos_t cpos = _mfat_cluster_pos_init_from_file(part, f);
1318 uint32_t bytes_read = 0U;
1320 // Align the head of the operation to a block boundary.
1321 uint32_t block_offset = f->offset % MFAT_BLOCK_SIZE;
1322 if (block_offset != 0U) {
1323 // Use the block cache to get a partial block.
1324 mfat_cached_block_t* block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1325 if (block == NULL) {
1326 DBG("Unable to read block");
1327 return -1;
1330 // Copy the data from the cache to the target buffer.
1331 uint32_t tail_bytes_in_block = MFAT_BLOCK_SIZE - block_offset;
1332 uint32_t bytes_to_copy = _mfat_min(tail_bytes_in_block, nbyte);
1333 memcpy(buf, &block->buf[block_offset], bytes_to_copy);
1334 DBGF("read: Head read of %" PRIu32 " bytes", bytes_to_copy);
1336 buf += bytes_to_copy;
1337 bytes_read += bytes_to_copy;
1339 // Move to the next block if we have read all the bytes of the block.
1340 if (bytes_to_copy == tail_bytes_in_block) {
1341 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1342 return -1;
1347 // Read aligned blocks directly into the target buffer.
1348 while ((nbyte - bytes_read) >= MFAT_BLOCK_SIZE) {
1349 if (_mfat_is_eoc(cpos.cluster_no)) {
1350 DBG("Unexpected cluster access after EOC");
1351 return -1;
1354 DBGF("read: Direct read of %d bytes", MFAT_BLOCK_SIZE);
1355 if (s_ctx.read((char*)buf, _mfat_cluster_pos_blk_no(&cpos), s_ctx.custom) == -1) {
1356 DBG("Unable to read block");
1357 return -1;
1359 buf += MFAT_BLOCK_SIZE;
1360 bytes_read += MFAT_BLOCK_SIZE;
1362 // Move to the next block.
1363 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1364 return -1;
1368 // Handle the tail of the operation (unaligned tail).
1369 if (bytes_read < nbyte) {
1370 if (_mfat_is_eoc(cpos.cluster_no)) {
1371 DBG("Unexpected cluster access after EOC");
1372 return -1;
1375 // Use the block cache to get a partial block.
1376 mfat_cached_block_t* block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1377 if (block == NULL) {
1378 DBG("Unable to read block");
1379 return -1;
1382 // Copy the data from the cache to the target buffer.
1383 uint32_t bytes_to_copy = nbyte - bytes_read;
1384 memcpy(buf, &block->buf[0], bytes_to_copy);
1385 DBGF("read: Tail read of %" PRIu32 " bytes", bytes_to_copy);
1387 bytes_read += bytes_to_copy;
1390 // Update file state.
1391 f->current_cluster = cpos.cluster_no;
1392 f->offset += bytes_read;
1394 return bytes_read;
1397 #if MFAT_ENABLE_WRITE
1398 static int64_t _mfat_write_impl(mfat_file_t* f, const uint8_t* buf, uint32_t nbyte) {
1399 // Is the file open with write permissions?
1400 if ((f->oflag & MFAT_O_WRONLY) == 0) {
1401 return -1;
1404 // TODO(m): Implement me!
1405 (void)buf;
1406 (void)nbyte;
1407 DBG("_mfat_write_impl() - not yet implemented");
1408 return -1;
1410 #endif
1412 static int64_t _mfat_lseek_impl(mfat_file_t* f, int64_t offset, int whence) {
1413 // Calculate the new requested file offset (the arithmetic is done in 64-bit precision in order to
1414 // properly handle overflow).
1415 int64_t target_offset_64;
1416 switch (whence) {
1417 case MFAT_SEEK_SET:
1418 target_offset_64 = offset;
1419 break;
1420 case MFAT_SEEK_END:
1421 target_offset_64 = ((int64_t)f->info.size) + offset;
1422 break;
1423 case MFAT_SEEK_CUR:
1424 target_offset_64 = ((int64_t)f->offset) + offset;
1425 break;
1426 default:
1427 DBGF("Invalid whence: %d", whence);
1428 return -1;
1431 // Sanity check the new offset.
1432 if (target_offset_64 < 0) {
1433 DBG("Seeking to a negative offset is not allowed");
1434 return -1;
1436 if (target_offset_64 > (int64_t)f->info.size) {
1437 // TODO(m): POSIX lseek() allows seeking beyond the end of the file. We currently don't.
1438 DBG("Seeking beyond the end of the file is not allowed");
1439 return -1;
1442 // This type conversion is safe, since we have verified that the value fits in a 32 bit unsigned
1443 // value.
1444 uint32_t target_offset = (uint32_t)target_offset_64;
1446 // Get partition info.
1447 mfat_partition_t* part = &s_ctx.partition[f->info.part_no];
1448 uint32_t bytes_per_cluster = part->blocks_per_cluster * MFAT_BLOCK_SIZE;
1450 // Define the starting point for the cluster search.
1451 uint32_t current_cluster = f->current_cluster;
1452 uint32_t cluster_offset = f->offset - (f->offset % bytes_per_cluster);
1453 if (target_offset < cluster_offset) {
1454 // For reverse seeking we need to start from the beginning of the file since FAT uses singly
1455 // linked lists.
1456 current_cluster = f->info.first_cluster;
1457 cluster_offset = 0;
1460 // Skip along clusters until we find the cluster that contains the requested offset.
1461 while ((target_offset - cluster_offset) >= bytes_per_cluster) {
1462 if (_mfat_is_eoc(current_cluster)) {
1463 DBG("Unexpected cluster access after EOC");
1464 return -1;
1467 // Look up the next cluster.
1468 if (!_mfat_next_cluster(part, &current_cluster)) {
1469 return -1;
1471 cluster_offset += bytes_per_cluster;
1474 // Update the current offset in the file descriptor.
1475 f->offset = target_offset;
1476 f->current_cluster = current_cluster;
1478 return (int64_t)target_offset;
1481 #if MFAT_ENABLE_OPENDIR
1482 static mfat_dir_t* _mfat_opendir_impl(int fd) {
1483 // Find the next free dir object.
1484 int dir_id;
1485 for (dir_id = 0; dir_id < MFAT_NUM_DIRS; ++dir_id) {
1486 if (s_ctx.dir[dir_id].file == NULL) {
1487 break;
1490 if (dir_id >= MFAT_NUM_DIRS) {
1491 DBG("No free dir:s left");
1492 return NULL;
1495 // Initialize the directory stream object.
1496 mfat_dir_t* dirp = &s_ctx.dir[dir_id];
1497 dirp->file = _mfat_fd_to_file(fd);
1498 if (dirp->file == NULL) {
1499 DBG("The dir fd is not an open dir");
1500 return NULL;
1502 mfat_partition_t* part = &s_ctx.partition[dirp->file->info.part_no];
1503 if (dirp->file->type == MFAT_FILE_TYPE_FAT16ROOTDIR) {
1504 // We use a fake/tweaked cluster pos for FAT16 root directories.
1505 dirp->cpos.cluster_no = 0U;
1506 dirp->cpos.cluster_start_blk = part->root_dir_block;
1507 dirp->cpos.block_in_cluster = 0U;
1508 dirp->blocks_left = part->blocks_in_root_dir;
1509 } else {
1510 // We use an "infinite" block count for regular cluster chain dirs.
1511 dirp->cpos = _mfat_cluster_pos_init(part, dirp->file->info.first_cluster, 0);
1512 dirp->blocks_left = 0xffffffffU;
1514 dirp->block_offset = 0U;
1516 // TODO(m): Pointer to first file in directory etc.
1517 dirp->items_left = 5; // HACK!
1519 return dirp;
1521 #endif
1523 #if MFAT_ENABLE_OPENDIR
1524 int _mfat_closedir_impl(mfat_dir_t* dirp) {
1525 if (dirp == NULL) {
1526 return -1;
1528 if (dirp->file == NULL) {
1529 DBG("The dir is already closed");
1530 return -1;
1533 // Close the file.
1534 int result = _mfat_close_impl(dirp->file);
1536 // The dir is no longer open. This makes the dir object available for future opendir() requests.
1537 dirp->file = NULL;
1539 return result;
1541 #endif
1543 #if MFAT_ENABLE_OPENDIR
1544 mfat_dirent_t* _mfat_readdir_impl(mfat_dir_t* dirp) {
1545 // Do we need to advance to the next block in the directory?
1546 if (dirp->block_offset >= 512U) {
1547 if (dirp->file->type == MFAT_FILE_TYPE_FAT16ROOTDIR) {
1548 dirp->cpos.block_in_cluster += 1; // FAT16 style linear block access.
1549 } else {
1550 mfat_partition_t* part = &s_ctx.partition[dirp->file->info.part_no];
1551 if (!_mfat_cluster_pos_advance(&dirp->cpos, part)) {
1552 DBG("readdir: Unable to advance to next cluster.");
1553 return NULL;
1556 dirp->block_offset = 0U;
1559 // Look up the next file name in the directory.
1560 mfat_bool_t found_entry = false;
1561 mfat_bool_t no_more_entries = false;
1562 for (; !found_entry && !no_more_entries && dirp->blocks_left > 0U; --dirp->blocks_left) {
1563 // Load the directory table block.
1564 mfat_cached_block_t* block =
1565 _mfat_read_block(_mfat_cluster_pos_blk_no(&dirp->cpos), MFAT_CACHE_DATA);
1566 if (block == NULL) {
1567 DBGF("Unable to load directory block %" PRIu32, _mfat_cluster_pos_blk_no(&dirp->cpos));
1568 return false;
1570 uint8_t* buf = &block->buf[0];
1572 // Loop over all the files in this directory block.
1573 for (; !found_entry && dirp->block_offset < 512U; dirp->block_offset += 32U) {
1574 uint8_t* entry = &buf[dirp->block_offset];
1576 // Empty entry?
1577 if (entry[0] == 0xe5) {
1578 continue;
1581 // Last entry in the directory structure?
1582 if (entry[0] == 0x00) {
1583 no_more_entries = true;
1584 break;
1587 // A file/dir entry?
1588 if (_mfat_is_valid_shortname_file(entry)) {
1589 // We found the next file.
1590 found_entry = true;
1591 _mfat_make_printable_fname(entry, dirp->dirent.d_name);
1596 if (!found_entry) {
1597 return NULL;
1600 return &dirp->dirent;
1602 #endif
1604 //--------------------------------------------------------------------------------------------------
1605 // Public API functions.
1606 //--------------------------------------------------------------------------------------------------
1608 int mfat_mount(mfat_read_block_fun_t read_fun, mfat_write_block_fun_t write_fun, void* custom) {
1609 #if MFAT_ENABLE_WRITE
1610 if (read_fun == NULL || write_fun == NULL) {
1611 #else
1612 if (read_fun == NULL) {
1613 #endif
1614 DBG("Bad function pointers");
1615 return -1;
1618 // Clear the context state.
1619 memset(&s_ctx, 0, sizeof(mfat_ctx_t));
1620 s_ctx.read = read_fun;
1621 #if MFAT_ENABLE_WRITE
1622 s_ctx.write = write_fun;
1623 #else
1624 (void)write_fun;
1625 #endif
1626 s_ctx.custom = custom;
1627 s_ctx.active_partition = -1;
1629 #if MFAT_NUM_CACHED_BLOCKS > 1
1630 // Initialize the block cache priority queues.
1631 for (int j = 0; j < MFAT_NUM_CACHES; ++j) {
1632 mfat_cache_t* cache = &s_ctx.cache[j];
1633 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
1634 // Assign initial items to the priority queue.
1635 cache->pri[i] = i;
1638 #endif
1640 // Read the partition tables.
1641 if (!_mfat_decode_partition_tables()) {
1642 return -1;
1645 // Find the first bootable partition. If no bootable partition is found, pick the first supported
1646 // partition.
1647 int first_boot_partition = -1;
1648 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
1649 if (s_ctx.partition[i].type != MFAT_PART_TYPE_UNKNOWN) {
1650 if (s_ctx.partition[i].boot && first_boot_partition < 0) {
1651 first_boot_partition = i;
1652 s_ctx.active_partition = i;
1653 } else if (s_ctx.active_partition < 0) {
1654 s_ctx.active_partition = i;
1658 if (s_ctx.active_partition < 0) {
1659 return -1;
1661 DBGF("Selected partition %d", s_ctx.active_partition);
1663 // MFAT is now initizlied.
1664 DBG("Successfully initialized");
1665 s_ctx.initialized = true;
1667 return 0;
1670 void mfat_unmount(void) {
1671 #if MFAT_ENABLE_WRITE
1672 // Flush any pending writes.
1673 _mfat_sync_impl();
1674 #endif
1675 s_ctx.initialized = false;
1678 int mfat_select_partition(int partition_no) {
1679 if (!s_ctx.initialized) {
1680 DBG("Not initialized");
1681 return -1;
1683 if (partition_no < 0 || partition_no >= MFAT_NUM_PARTITIONS) {
1684 DBGF("Bad partition number: %d", partition_no);
1685 return -1;
1687 if (s_ctx.partition[partition_no].type == MFAT_PART_TYPE_UNKNOWN) {
1688 DBG("Unsupported partition type");
1689 return -1;
1692 s_ctx.active_partition = partition_no;
1694 return 0;
1697 void mfat_sync(void) {
1698 #if MFAT_ENABLE_WRITE
1699 if (!s_ctx.initialized) {
1700 DBG("Not initialized");
1701 return;
1704 _mfat_sync_impl();
1705 #endif
1708 int mfat_fstat(int fd, mfat_stat_t* stat) {
1709 if (!s_ctx.initialized) {
1710 DBG("Not initialized");
1711 return -1;
1714 mfat_file_t* f = _mfat_fd_to_file(fd);
1715 if (f == NULL) {
1716 return -1;
1719 return _mfat_fstat_impl(&f->info, stat);
1722 int mfat_stat(const char* path, mfat_stat_t* stat) {
1723 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1724 DBG("Not initialized");
1725 return -1;
1727 if (path == NULL || stat == NULL) {
1728 return -1;
1731 return _mfat_stat_impl(path, stat);
1734 int mfat_open(const char* path, int oflag) {
1735 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1736 DBG("Not initialized");
1737 return -1;
1739 if (path == NULL || ((oflag & MFAT_O_RDWR) == 0)) {
1740 return -1;
1743 return _mfat_open_impl(path, oflag);
1746 int mfat_close(int fd) {
1747 if (!s_ctx.initialized) {
1748 DBG("Not initialized");
1749 return -1;
1752 mfat_file_t* f = _mfat_fd_to_file(fd);
1753 if (f == NULL) {
1754 return -1;
1757 return _mfat_close_impl(f);
1760 int64_t mfat_read(int fd, void* buf, uint32_t nbyte) {
1761 if (!s_ctx.initialized) {
1762 DBG("Not initialized");
1763 return -1;
1766 mfat_file_t* f = _mfat_fd_to_file(fd);
1767 if (f == NULL || f->type != MFAT_FILE_TYPE_REGULAR) {
1768 return -1;
1771 return _mfat_read_impl(f, (uint8_t*)buf, nbyte);
1774 int64_t mfat_write(int fd, const void* buf, uint32_t nbyte) {
1775 #if MFAT_ENABLE_WRITE
1776 if (!s_ctx.initialized) {
1777 DBG("Not initialized");
1778 return -1;
1781 mfat_file_t* f = _mfat_fd_to_file(fd);
1782 if (f == NULL || f->type != MFAT_FILE_TYPE_REGULAR) {
1783 return -1;
1786 return _mfat_write_impl(f, (const uint8_t*)buf, nbyte);
1787 #else
1788 DBG("mfat_write() was disabled at compile-time");
1789 (void)fd;
1790 (void)buf;
1791 (void)nbyte;
1792 return -1;
1793 #endif
1796 int64_t mfat_lseek(int fd, int64_t offset, int whence) {
1797 if (!s_ctx.initialized) {
1798 DBG("Not initialized");
1799 return -1;
1802 mfat_file_t* f = _mfat_fd_to_file(fd);
1803 if (f == NULL || f->type != MFAT_FILE_TYPE_REGULAR) {
1804 return -1;
1807 return _mfat_lseek_impl(f, offset, whence);
1810 mfat_dir_t* mfat_fdopendir(int fd) {
1811 #if MFAT_ENABLE_OPENDIR
1812 if (!s_ctx.initialized) {
1813 DBG("Not initialized");
1814 return NULL;
1817 return _mfat_opendir_impl(fd);
1818 #else
1819 DBG("mfat_opendir() was disabled at compile-time");
1820 (void)fd;
1821 return NULL;
1822 #endif
1825 mfat_dir_t* mfat_opendir(const char* path) {
1826 #if MFAT_ENABLE_OPENDIR
1827 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1828 DBG("Not initialized");
1829 return NULL;
1832 int fd = mfat_open(path, MFAT_O_DIRECTORY | MFAT_O_RDONLY);
1833 if (fd == -1) {
1834 return NULL;
1836 return _mfat_opendir_impl(fd);
1837 #else
1838 (void)path;
1839 return NULL;
1840 #endif
1843 int mfat_closedir(mfat_dir_t* dirp) {
1844 #if MFAT_ENABLE_OPENDIR
1845 if (!s_ctx.initialized) {
1846 DBG("Not initialized");
1847 return -1;
1850 return _mfat_closedir_impl(dirp);
1851 #else
1852 (void)dirp;
1853 return -1;
1854 #endif
1857 mfat_dirent_t* mfat_readdir(mfat_dir_t* dirp) {
1858 #if MFAT_ENABLE_OPENDIR
1859 if (!s_ctx.initialized) {
1860 DBG("Not initialized");
1861 return NULL;
1863 if (dirp == NULL || dirp->file == NULL) {
1864 DBG("Invalid dir");
1865 return NULL;
1868 // Advance to the next entry in the directory.
1869 return _mfat_readdir_impl(dirp);
1870 #else
1871 (void)dirp;
1872 return NULL;
1873 #endif