Support "." and ".." folders
[mfat.git] / mfat.c
blobdf753e9ccc1ee0030a4e71cd3f5d55a48add381a
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_PART_TYPE_UNKNOWN 0
123 #define MFAT_PART_TYPE_FAT_UNDECIDED 1 // A FAT partion of yet unknown type (FAT16 or FAT32).
124 #define MFAT_PART_TYPE_FAT16 2
125 #define MFAT_PART_TYPE_FAT32 3
127 // mfat_file_t::type
128 #define MFAT_FILE_TYPE_REGULAR 0 // Regular file.
129 #define MFAT_FILE_TYPE_DIR 1 // A directory.
130 #define MFAT_FILE_TYPE_FAT16ROOTDIR 2 // A FAT16 root directory (which is special).
132 // A collection of variables for keeping track of the current cluster & block position, e.g. during
133 // read/write operations.
134 typedef struct {
135 uint32_t cluster_no; ///< The cluster number.
136 uint32_t block_in_cluster; ///< The block offset within the cluster (0..blocks_per_cluster-1).
137 uint32_t cluster_start_blk; ///< Absolute block number of the first block of the cluster.
138 } mfat_cluster_pos_t;
140 typedef struct {
141 uint32_t type;
142 uint32_t first_block;
143 uint32_t num_blocks;
144 uint32_t blocks_per_cluster;
145 #if MFAT_ENABLE_WRITE
146 uint32_t num_clusters;
147 #endif
148 uint32_t blocks_per_fat;
149 uint32_t num_fats;
150 uint32_t num_reserved_blocks;
151 uint32_t root_dir_block; // Used for FAT16.
152 uint32_t blocks_in_root_dir; // Used for FAT16 (zero for FAT32).
153 uint32_t root_dir_cluster; // Used for FAT32.
154 uint32_t first_data_block;
155 mfat_bool_t boot;
156 } mfat_partition_t;
158 // Static information about a file, as specified in the file system.
159 typedef struct {
160 int part_no; // Partition that this file is located on.
161 uint32_t size; // Total size of file, in bytes.
162 uint32_t first_cluster; // Starting cluster for the file.
163 uint32_t dir_entry_block; // Block number for the directory entry of this file.
164 uint32_t dir_entry_offset; // Offset (in bytes) into the directory entry block.
165 } mfat_file_info_t;
167 // File handle, corresponding to a file descriptor (fd).
168 typedef struct {
169 mfat_bool_t open; // Is the file open?
170 int type; // File type (e.g. MFAT_FILE_TYPE_REGULAR or MFAT_FILE_TYPE_DIR).
171 int oflag; // Flags used when opening the file.
172 uint32_t offset; // Current byte offset relative to the file start (seek offset).
173 uint32_t current_cluster; // Current cluster (representing the current seek offset)
174 mfat_file_info_t info;
175 } mfat_file_t;
177 typedef struct {
178 int state;
179 uint32_t blk_no;
180 uint8_t buf[MFAT_BLOCK_SIZE];
181 } mfat_cached_block_t;
183 typedef struct {
184 mfat_cached_block_t block[MFAT_NUM_CACHED_BLOCKS];
185 #if MFAT_NUM_CACHED_BLOCKS > 1
186 // This is a priority queue: The last item in the queue is an index to the
187 // least recently used cached block item.
188 int pri[MFAT_NUM_CACHED_BLOCKS];
189 #endif
190 } mfat_cache_t;
192 typedef struct {
193 mfat_bool_t initialized;
194 int active_partition;
195 mfat_read_block_fun_t read;
196 #if MFAT_ENABLE_WRITE
197 mfat_write_block_fun_t write;
198 #endif
199 void* custom;
200 mfat_partition_t partition[MFAT_NUM_PARTITIONS];
201 mfat_file_t file[MFAT_NUM_FDS];
202 mfat_cache_t cache[MFAT_NUM_CACHES];
203 } mfat_ctx_t;
205 // Statically allocated state.
206 static mfat_ctx_t s_ctx;
208 //--------------------------------------------------------------------------------------------------
209 // Private functions.
210 //--------------------------------------------------------------------------------------------------
212 static inline uint32_t _mfat_min(uint32_t a, uint32_t b) {
213 return (a < b) ? a : b;
216 static uint32_t _mfat_get_word(const uint8_t* buf) {
217 return ((uint32_t)buf[0]) | (((uint32_t)buf[1]) << 8);
220 static uint32_t _mfat_get_dword(const uint8_t* buf) {
221 return ((uint32_t)buf[0]) | (((uint32_t)buf[1]) << 8) | (((uint32_t)buf[2]) << 16) |
222 (((uint32_t)buf[3]) << 24);
225 static mfat_bool_t _mfat_cmpbuf(const uint8_t* a, const uint8_t* b, const uint32_t nbyte) {
226 for (uint32_t i = 0; i < nbyte; ++i) {
227 if (a[i] != b[i]) {
228 return false;
231 return true;
234 #if MFAT_ENABLE_MBR
235 static mfat_bool_t _mfat_is_fat_part_id(uint32_t id) {
236 switch (id) {
237 case MFAT_PART_ID_FAT16_LT32GB:
238 case MFAT_PART_ID_FAT16_GT32GB:
239 case MFAT_PART_ID_FAT16_GT32GB_LBA:
240 case MFAT_PART_ID_FAT32:
241 case MFAT_PART_ID_FAT32_LBA:
242 return true;
243 default:
244 return false;
247 #endif // MFAT_ENABLE_MBR
249 #if MFAT_ENABLE_GPT
250 static mfat_bool_t _mfat_is_fat_part_guid(const uint8_t* guid) {
251 // Windows Basic data partition GUID = EBD0A0A2-B9E5-4433-87C0-68B6B72699C7
252 static const uint8_t BDP_GUID[] = {0xa2,
253 0xa0,
254 0xd0,
255 0xeb,
256 0xe5,
257 0xb9,
258 0x33,
259 0x44,
260 0x87,
261 0xc0,
262 0x68,
263 0xb6,
264 0xb7,
265 0x26,
266 0x99,
267 0xc7};
268 if (_mfat_cmpbuf(guid, &BDP_GUID[0], sizeof(BDP_GUID))) {
269 // TODO(m): These may also be NTFS partitions. How to detect?
270 return true;
272 return false;
274 #endif // MFAT_ENABLE_GPT
276 static mfat_bool_t _mfat_is_valid_bpb(const uint8_t* bpb_buf) {
277 // Check that the BPB signature is there.
278 if (bpb_buf[510] != 0x55U || bpb_buf[511] != 0xaaU) {
279 DBGF("\t\tInvalid BPB signature: [%02x,%02x]", bpb_buf[510], bpb_buf[511]);
280 return false;
283 // Check for a valid jump instruction (first three bytes).
284 if (bpb_buf[0] != 0xe9 && !(bpb_buf[0] == 0xeb && bpb_buf[2] == 0x90)) {
285 DBGF("\t\tInvalid BPB jump code: [%02x,%02x,%02x]", bpb_buf[0], bpb_buf[1], bpb_buf[2]);
286 return false;
289 // Check for valid bytes per sector.
290 uint32_t bps = _mfat_get_word(&bpb_buf[11]);
291 if (bps != 512 && bps != 1024 && bps != 2048 && bps != 4096) {
292 DBGF("\t\tInvalid BPB bytes per sector: %" PRIu32, bps);
293 return false;
296 return true;
299 static mfat_bool_t _mfat_is_valid_shortname_file(const uint8_t* dir_entry) {
300 const uint8_t attr = dir_entry[11];
301 if (((attr & MFAT_ATTR_LONG_NAME) == MFAT_ATTR_LONG_NAME) ||
302 ((attr & MFAT_ATTR_VOLUME_ID) == MFAT_ATTR_VOLUME_ID)) {
303 return false;
305 return true;
308 static mfat_cached_block_t* _mfat_get_cached_block(uint32_t blk_no, int cache_type) {
309 // Pick the relevant cache.
310 mfat_cache_t* cache = &s_ctx.cache[cache_type];
312 #if MFAT_NUM_CACHED_BLOCKS > 1
313 // By default, pick the last (least recently used) item in the pirority queue...
314 int item_id = cache->pri[MFAT_NUM_CACHED_BLOCKS - 1];
316 // ...but override it if we have a cache hit.
317 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
318 mfat_cached_block_t* cb = &cache->block[i];
319 if (cb->state != MFAT_INVALID && cb->blk_no == blk_no) {
320 item_id = i;
321 break;
325 // Move the selected item to the front of the priority queue (i.e. it's the most recently used
326 // cache item).
328 int prev_id = item_id;
329 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
330 int this_id = cache->pri[i];
331 cache->pri[i] = prev_id;
332 if (this_id == item_id) {
333 break;
335 prev_id = this_id;
339 mfat_cached_block_t* cached_block = &cache->block[item_id];
340 #else
341 mfat_cached_block_t* cached_block = &cache->block[0];
342 #endif
344 // Reassign the cached block to the requested block number (if necessary).
345 if (cached_block->blk_no != blk_no) {
346 #if MFAT_ENABLE_DEBUG
347 if (cached_block->state != MFAT_INVALID) {
348 DBGF("Cache %d: Evicting block %" PRIu32 " in favor of block %" PRIu32,
349 cache_type,
350 cached_block->blk_no,
351 blk_no);
353 #endif
355 #if MFAT_ENABLE_WRITE
356 // Flush the block?
357 if (cached_block->state == MFAT_DIRTY) {
358 DBGF("Cache %d: Flushing evicted block %" PRIu32, cache_type, cached_block->blk_no);
359 if (s_ctx.write((const char*)cached_block->buf, cached_block->blk_no, s_ctx.custom) == -1) {
360 // FATAL: We can't recover from here... :-(
361 DBGF("Cache %d: Failed to flush the block", cache_type);
362 return NULL;
365 #endif
367 // Set the new block ID.
368 cached_block->blk_no = blk_no;
370 // The contents of the buffer is now invalid.
371 cached_block->state = MFAT_INVALID;
374 return cached_block;
377 static mfat_cached_block_t* _mfat_read_block(uint32_t block_no, int cache_type) {
378 // First query the cache.
379 mfat_cached_block_t* block = _mfat_get_cached_block(block_no, cache_type);
380 if (block == NULL) {
381 return NULL;
384 // If necessary, read the block from storage.
385 if (block->state == MFAT_INVALID) {
386 if (s_ctx.read((char*)block->buf, block_no, s_ctx.custom) == -1) {
387 return NULL;
389 block->state = MFAT_VALID;
392 return block;
395 // Helper function for finding the next cluster in a cluster chain.
396 static mfat_bool_t _mfat_next_cluster(const mfat_partition_t* part, uint32_t* cluster) {
397 const uint32_t fat_entry_size = (part->type == MFAT_PART_TYPE_FAT32) ? 4 : 2;
399 uint32_t fat_offset = fat_entry_size * (*cluster);
400 uint32_t fat_block =
401 part->first_block + part->num_reserved_blocks + (fat_offset / MFAT_BLOCK_SIZE);
402 uint32_t fat_block_offset = fat_offset % MFAT_BLOCK_SIZE;
404 // For FAT copy no. N (0..num_fats-1):
405 // fat_block += N * part->blocks_per_fat
407 // Read the FAT block into a cached buffer.
408 mfat_cached_block_t* block = _mfat_read_block(fat_block, MFAT_CACHE_FAT);
409 if (block == NULL) {
410 DBGF("Failed to read the FAT block %" PRIu32, fat_block);
411 return false;
413 uint8_t* buf = &block->buf[0];
415 // Get the value for this cluster from the FAT.
416 uint32_t next_cluster;
417 if (part->type == MFAT_PART_TYPE_FAT32) {
418 // For FAT32 we mask off upper 4 bits, as the cluster number is 28 bits.
419 next_cluster = _mfat_get_dword(&buf[fat_block_offset]) & 0x0fffffffU;
420 } else {
421 next_cluster = _mfat_get_word(&buf[fat_block_offset]);
422 if (next_cluster >= 0xfff7U) {
423 // Convert FAT16 special codes (BAD & EOC) to FAT32 codes.
424 next_cluster |= 0x0fff0000U;
428 // This is a sanity check (failure indicates a corrupt filesystem). We should really do this check
429 // BEFORE accessing the cluster instead.
430 // We do not expect to see:
431 // 0x00000000 - free cluster
432 // 0x0ffffff7 - BAD cluster
433 if (next_cluster == 0U || next_cluster == 0x0ffffff7U) {
434 DBGF("Unexpected next cluster: 0x%08" PRIx32, next_cluster);
435 return false;
438 DBGF("Next cluster: %" PRIu32 " (0x%08" PRIx32 ")", next_cluster, next_cluster);
440 // Return the next cluster number.
441 *cluster = next_cluster;
443 return true;
446 // Helper function for finding the first block of a cluster.
447 static uint32_t _mfat_first_block_of_cluster(const mfat_partition_t* part, uint32_t cluster) {
448 return part->first_data_block + ((cluster - 2U) * part->blocks_per_cluster);
451 // Initialize a cluster pos object to the current cluster & offset (e.g. for a file).
452 static mfat_cluster_pos_t _mfat_cluster_pos_init(const mfat_partition_t* part,
453 const uint32_t cluster_no,
454 const uint32_t offset) {
455 mfat_cluster_pos_t cpos;
456 cpos.cluster_no = cluster_no;
457 cpos.block_in_cluster = (offset % (part->blocks_per_cluster * MFAT_BLOCK_SIZE)) / MFAT_BLOCK_SIZE;
458 cpos.cluster_start_blk = _mfat_first_block_of_cluster(part, cluster_no);
459 return cpos;
462 // Initialize a cluster pos object to the current file offset of the specified file.
463 static mfat_cluster_pos_t _mfat_cluster_pos_init_from_file(const mfat_partition_t* part,
464 const mfat_file_t* f) {
465 return _mfat_cluster_pos_init(part, f->current_cluster, f->offset);
468 // Advance a cluster pos by one block.
469 static mfat_bool_t _mfat_cluster_pos_advance(mfat_cluster_pos_t* cpos,
470 const mfat_partition_t* part) {
471 ++cpos->block_in_cluster;
472 if (cpos->block_in_cluster == part->blocks_per_cluster) {
473 if (!_mfat_next_cluster(part, &cpos->cluster_no)) {
474 return false;
476 cpos->cluster_start_blk = _mfat_first_block_of_cluster(part, cpos->cluster_no);
477 cpos->block_in_cluster = 0;
479 return true;
482 // Get the current absolute block of a cluster pos object.
483 static uint32_t _mfat_cluster_pos_blk_no(const mfat_cluster_pos_t* cpos) {
484 return cpos->cluster_start_blk + cpos->block_in_cluster;
487 // Check for EOC (End of clusterchain). NOTE: EOC cluster is included in file.
488 static mfat_bool_t _mfat_is_eoc(const uint32_t cluster) {
489 return cluster >= 0x0ffffff8U;
492 #if MFAT_ENABLE_GPT
493 static mfat_bool_t _mfat_decode_gpt(void) {
494 // Read the primary GUID Partition Table (GPT) header, located at block 1.
495 mfat_cached_block_t* block = _mfat_read_block(1U, MFAT_CACHE_DATA);
496 if (block == NULL) {
497 DBG("Failed to read the GPT");
498 return false;
500 uint8_t* buf = &block->buf[0];
502 // Is this in fact an GPT header?
503 // TODO(m): We could do more validation (CRC etc).
504 static const uint8_t gpt_sig[] = {0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54};
505 if (!_mfat_cmpbuf(&buf[0], &gpt_sig[0], sizeof(gpt_sig))) {
506 DBG("Not a valid GPT signature");
507 return false;
510 // Get the start LBA of the the partition entries.
511 // Note: This is actually a 64-bit value, but we don't support such large media anyway, and by
512 // reading it out as a 32-bit little endian number we get the correct value IF the upper 32
513 // bits are zero. This should be OK as the value should always be 0x0000000000000002 for the
514 // primary copy, which we are interested in.
515 uint32_t entries_block = _mfat_get_dword(&buf[72]);
517 // Get the number of partition entries.
518 uint32_t num_entries = _mfat_get_dword(&buf[80]);
520 // Get the size of each partition entry, in bytes.
521 uint32_t entry_size = _mfat_get_dword(&buf[84]);
523 DBGF("GPT header: entries_block=%" PRIu32 ", num_entries=%" PRIu32 ", entry_size=%" PRIu32,
524 entries_block,
525 num_entries,
526 entry_size);
528 uint32_t entry_offs = 0;
529 for (uint32_t i = 0; i < num_entries && i < MFAT_NUM_PARTITIONS; ++i) {
530 // Read the next block of the partition entry array if necessary.
531 if ((entry_offs % MFAT_BLOCK_SIZE) == 0) {
532 block = _mfat_read_block(entries_block, MFAT_CACHE_DATA);
533 if (block == NULL) {
534 DBGF("Failed to read the GPT partition entry array at block %" PRIu32, entries_block);
535 return false;
537 buf = &block->buf[0];
539 ++entries_block;
540 entry_offs = 0;
543 // Decode the partition entry.
544 uint8_t* entry = &buf[entry_offs];
545 mfat_partition_t* part = &s_ctx.partition[i];
547 // Get the starting block of the partition (again, 64-bit value that we read as a 32-bit
548 // value).
549 part->first_block = _mfat_get_dword(&entry[32]);
551 // Check if the partition is bootable (bit ).
552 part->boot = ((entry[48] & 0x04) != 0);
554 // Is this a potential FAT partition?
555 if (_mfat_is_fat_part_guid(&entry[0])) {
556 // The actual FAT type is determined later.
557 part->type = MFAT_PART_TYPE_FAT_UNDECIDED;
560 // Note: We print GUIDs in byte order, as we expect them in our signatures, NOT in GUID
561 // mixed endian order.
562 DBGF("GPT entry %" PRIu32 ": first_block = %" PRIu32 ", GUID = %08" PRIx32 "%08" PRIx32
563 "%08" PRIx32 "%08" PRIx32 ", FAT = %s",
565 part->first_block,
566 _mfat_get_dword(&entry[12]),
567 _mfat_get_dword(&entry[8]),
568 _mfat_get_dword(&entry[4]),
569 _mfat_get_dword(&entry[0]),
570 part->type != MFAT_PART_TYPE_UNKNOWN ? "Yes" : "No");
572 entry_offs += entry_size;
575 return true;
577 #endif // MFAT_ENABLE_GPT
579 #if MFAT_ENABLE_MBR
580 static mfat_bool_t _mfat_decode_mbr(void) {
581 mfat_cached_block_t* block = _mfat_read_block(0U, MFAT_CACHE_DATA);
582 if (block == NULL) {
583 DBG("Failed to read the MBR");
584 return false;
586 uint8_t* buf = &block->buf[0];
588 // Is this an MBR block (can also be a partition boot record)?
589 mfat_bool_t found_valid_mbr = false;
590 if (buf[510] == 0x55U && buf[511] == 0xaaU) {
591 // Parse each partition entry.
592 for (int i = 0; i < 4 && i < MFAT_NUM_PARTITIONS; ++i) {
593 uint8_t* entry = &buf[446U + 16U * (uint32_t)i];
594 mfat_partition_t* part = &s_ctx.partition[i];
596 // Current state of partition (00h=Inactive, 80h=Active).
597 part->boot = ((entry[0] & 0x80U) != 0);
599 // Is this a FAT partition.
600 if (_mfat_is_fat_part_id(entry[4])) {
601 // The actual FAT type is determined later.
602 part->type = MFAT_PART_TYPE_FAT_UNDECIDED;
603 found_valid_mbr = true;
606 // Get the beginning of the partition (LBA).
607 part->first_block = _mfat_get_dword(&entry[8]);
609 } else {
610 DBGF("Not a valid MBR signature: [%02x,%02x]", buf[510], buf[511]);
613 return found_valid_mbr;
615 #endif // MFAT_ENABLE_MBR
617 static void _mfat_decode_tableless(void) {
618 // Some storage media are formatted without an MBR or GPT. If so, there is only a single volume
619 // and the first block is the BPB (BIOS Parameter Block) of that "partition". We initially guess
620 // that this is the case, and let the partition decoding logic assert if this was a good guess.
621 DBG("Assuming that the storage medium does not have a partition table");
623 // Clear all partitions (their values are potentially garbage).
624 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
625 memset(&s_ctx.partition[i], 0, sizeof(mfat_partition_t));
628 // Guess that the first partition is FAT (we'll detect the actual type later).
629 // Note: The "first_block" field is cleared to zero in the previous memset, so the first
630 // partition starts at the first block, which is what we intend.
631 s_ctx.partition[0].type = MFAT_PART_TYPE_FAT_UNDECIDED;
634 static mfat_bool_t _mfat_decode_partition_tables(void) {
635 mfat_bool_t found_partition_table = false;
637 #if MFAT_ENABLE_GPT
638 // 1: Try to read the GUID Partition Table (GPT).
639 if (!found_partition_table) {
640 found_partition_table = _mfat_decode_gpt();
642 #endif
644 #if MFAT_ENABLE_MBR
645 // 2: Try to read the Master Boot Record (MBR).
646 if (!found_partition_table) {
647 found_partition_table = _mfat_decode_mbr();
649 #endif
651 // 3: Assume that the storage medium does not have a partition table at all.
652 if (!found_partition_table) {
653 _mfat_decode_tableless();
656 // Read and parse the BPB for each FAT partition.
657 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
658 DBGF("Partition %d:", i);
659 mfat_partition_t* part = &s_ctx.partition[i];
661 // Skip unsupported partition types.
662 if (part->type == MFAT_PART_TYPE_UNKNOWN) {
663 DBG("\t\tNot a FAT partition");
664 continue;
667 // Load the BPB (the first block of the partition).
668 mfat_cached_block_t* block = _mfat_read_block(part->first_block, MFAT_CACHE_DATA);
669 if (block == NULL) {
670 DBG("\t\tFailed to read the BPB");
671 return false;
673 uint8_t* buf = &block->buf[0];
675 if (!_mfat_is_valid_bpb(buf)) {
676 DBG("\t\tPartition does not appear to have a valid BPB");
677 part->type = MFAT_PART_TYPE_UNKNOWN;
678 continue;
681 // Decode useful metrics from the BPB (these are used in block number arithmetic etc).
682 uint32_t bytes_per_block = _mfat_get_word(&buf[11]);
683 part->blocks_per_cluster = buf[13];
684 part->num_reserved_blocks = _mfat_get_word(&buf[14]);
685 part->num_fats = buf[16];
687 uint32_t tot_sectors_16 = _mfat_get_word(&buf[19]);
688 uint32_t tot_sectors_32 = _mfat_get_dword(&buf[32]);
689 part->num_blocks = (tot_sectors_16 != 0) ? tot_sectors_16 : tot_sectors_32;
692 uint32_t blocks_per_fat_16 = _mfat_get_word(&buf[22]);
693 uint32_t blocks_per_fat_32 = _mfat_get_dword(&buf[36]);
694 part->blocks_per_fat = (blocks_per_fat_16 != 0) ? blocks_per_fat_16 : blocks_per_fat_32;
697 uint32_t num_root_entries = _mfat_get_word(&buf[17]);
698 part->blocks_in_root_dir =
699 ((num_root_entries * 32U) + (MFAT_BLOCK_SIZE - 1U)) / MFAT_BLOCK_SIZE;
702 // Derive useful metrics.
703 part->first_data_block = part->first_block + part->num_reserved_blocks +
704 (part->num_fats * part->blocks_per_fat) + part->blocks_in_root_dir;
706 // Check that the number of bytes per sector is 512.
707 // TODO(m): We could add support for larger block sizes.
708 if (bytes_per_block != MFAT_BLOCK_SIZE) {
709 DBGF("\t\tUnsupported block size: %" PRIu32, bytes_per_block);
710 part->type = MFAT_PART_TYPE_UNKNOWN;
711 continue;
714 // Up until now we have only been guessing the partition FAT type. Now we determine the *actual*
715 // partition type (FAT12, FAT16 or FAT32), using the detection algorithm given in the FAT32 File
716 // System Specification, p.14. The definition is based on the count of clusters as follows:
717 // FAT12: count of clusters < 4085
718 // FAT16: 4085 <= count of clusters < 65525
719 // FAT32: 65525 <= count of clusters
721 uint32_t root_ent_cnt = _mfat_get_word(&buf[17]);
722 uint32_t root_dir_sectors = ((root_ent_cnt * 32) + (MFAT_BLOCK_SIZE - 1)) / MFAT_BLOCK_SIZE;
724 uint32_t data_sectors =
725 part->num_blocks -
726 (part->num_reserved_blocks + (part->num_fats * part->blocks_per_fat) + root_dir_sectors);
728 uint32_t count_of_clusters = data_sectors / part->blocks_per_cluster;
730 #if MFAT_ENABLE_WRITE
731 // We need to know the actual number of clusters when writing files.
732 part->num_clusters = count_of_clusters + 1;
733 #endif
735 // We don't support FAT12.
736 if (count_of_clusters < 4085) {
737 part->type = MFAT_PART_TYPE_UNKNOWN;
738 DBG("\t\tFAT12 is not supported.");
739 continue;
742 if (count_of_clusters < 65525) {
743 part->type = MFAT_PART_TYPE_FAT16;
744 } else {
745 part->type = MFAT_PART_TYPE_FAT32;
749 // Determine the starting block/cluster for the root directory.
750 if (part->type == MFAT_PART_TYPE_FAT16) {
751 part->root_dir_block = part->first_data_block - part->blocks_in_root_dir;
752 } else {
753 part->root_dir_cluster = _mfat_get_dword(&buf[44]);
756 #if MFAT_ENABLE_DEBUG
757 // Print the partition information.
758 DBGF("\t\ttype = %s", part->type == MFAT_PART_TYPE_FAT16 ? "FAT16" : "FAT32");
759 DBGF("\t\tboot = %s", part->boot ? "Yes" : "No");
760 DBGF("\t\tfirst_block = %" PRIu32, part->first_block);
761 DBGF("\t\tbytes_per_block = %" PRIu32, bytes_per_block);
762 DBGF("\t\tnum_blocks = %" PRIu32, part->num_blocks);
763 DBGF("\t\tblocks_per_cluster = %" PRIu32, part->blocks_per_cluster);
764 #if MFAT_ENABLE_WRITE
765 DBGF("\t\tnum_clusters = %" PRIu32, part->num_clusters);
766 #endif
767 DBGF("\t\tblocks_per_fat = %" PRIu32, part->blocks_per_fat);
768 DBGF("\t\tnum_fats = %" PRIu32, part->num_fats);
769 DBGF("\t\tnum_reserved_blocks = %" PRIu32, part->num_reserved_blocks);
770 DBGF("\t\troot_dir_block = %" PRIu32, part->root_dir_block);
771 DBGF("\t\troot_dir_cluster = %" PRIu32, part->root_dir_cluster);
772 DBGF("\t\tblocks_in_root_dir = %" PRIu32, part->blocks_in_root_dir);
773 DBGF("\t\tfirst_data_block = %" PRIu32, part->first_data_block);
775 // Print the extended boot signature.
776 const uint8_t* ex_boot_sig = (part->type == MFAT_PART_TYPE_FAT32) ? &buf[66] : &buf[38];
777 if (ex_boot_sig[0] == 0x29) {
778 uint32_t vol_id = _mfat_get_dword(&ex_boot_sig[1]);
780 char label[12];
781 memcpy(&label[0], &ex_boot_sig[5], 11);
782 label[11] = 0;
784 char fs_type[9];
785 memcpy(&fs_type[0], &ex_boot_sig[16], 8);
786 fs_type[8] = 0;
788 DBGF("\t\tvol_id = %04" PRIx32 ":%04" PRIx32, (vol_id >> 16), vol_id & 0xffffU);
789 DBGF("\t\tlabel = \"%s\"", label);
790 DBGF("\t\tfs_type = \"%s\"", fs_type);
791 } else {
792 DBGF("\t\t(Boot signature N/A - bpb[%d]=0x%02x)", (int)(ex_boot_sig - buf), ex_boot_sig[0]);
794 #endif // MFAT_ENABLE_DEBUG
797 return true;
800 static mfat_file_t* _mfat_fd_to_file(int fd) {
801 if (fd < 0 || fd >= MFAT_NUM_FDS) {
802 DBGF("FD out of range: %d", fd);
803 return NULL;
805 mfat_file_t* f = &s_ctx.file[fd];
806 if (!f->open) {
807 DBGF("File is not open: %d", fd);
808 return NULL;
810 return f;
813 static void _mfat_dir_entry_to_stat(uint8_t* dir_entry, mfat_stat_t* stat) {
814 // Decode file attributes.
815 uint32_t attr = dir_entry[11];
816 uint32_t st_mode =
817 MFAT_S_IRUSR | MFAT_S_IRGRP | MFAT_S_IROTH | MFAT_S_IXUSR | MFAT_S_IXGRP | MFAT_S_IXOTH;
818 if ((attr & MFAT_ATTR_READ_ONLY) == 0) {
819 st_mode |= MFAT_S_IWUSR | MFAT_S_IWGRP | MFAT_S_IWOTH;
821 if ((attr & MFAT_ATTR_DIRECTORY) != 0) {
822 st_mode |= MFAT_S_IFDIR;
823 } else {
824 st_mode |= MFAT_S_IFREG;
826 stat->st_mode = st_mode;
828 // Decode time.
829 uint32_t time = _mfat_get_word(&dir_entry[22]);
830 stat->st_mtim.hour = time >> 11;
831 stat->st_mtim.minute = (time >> 5) & 63;
832 stat->st_mtim.second = (time & 31) * 2;
834 // Decode date.
835 uint32_t date = _mfat_get_word(&dir_entry[24]);
836 stat->st_mtim.year = (date >> 9) + 1980U;
837 stat->st_mtim.month = (date >> 5) & 15;
838 stat->st_mtim.day = date & 31;
840 // Get file size.
841 stat->st_size = _mfat_get_dword(&dir_entry[28]);
844 static int _mfat_canonicalize_char(int c) {
845 // Valid character?
846 if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '$' || c == '%' || c == '-' ||
847 c == '_' || c == '@' || c == '~' || c == '`' || c == '!' || c == '(' || c == ')' ||
848 c == '{' || c == '}' || c == '^' || c == '#' || c == '&') {
849 return c;
852 // Convert lower case to upper case.
853 if (c >= 'a' && c <= 'z') {
854 return c - 'a' + 'A';
857 // Invalid character.
858 return '!';
861 /// @brief Canonicalize a file name.
863 /// Convert the next path part (up until a directory separator or the end of the string) into a
864 /// valid 8.3 short file name as represented in a FAT directory entry (i.e. space padded and without
865 /// the dot). File names that do not fit in an 8.3 representation will be silently truncated.
867 /// Examples:
868 /// - "hello.txt" -> "HELLO TXT"
869 /// - "File.1" -> "FILE 1 "
870 /// - "ALongFileName.json" -> "ALONGFILJSO"
871 /// - "bin/foo.exe" -> "BIN " (followed by "FOO EXE" at the next invocation)
872 /// - "." -> ". "
873 /// - ".." -> ".. "
874 /// @param path A path to a file, possibly includuing directory separators (/ or \).
875 /// @param[out] fname The canonicalized file name.
876 /// @returns the index of the next path part, or -1 if this was the last path part.
877 static int _mfat_canonicalize_fname(const char* path, char name[12]) {
878 int pos = 0;
879 int npos = 0;
880 int c;
882 // Special cases: "." and ".." (unlike other file names, the dots are spelled out in these names).
883 if (path[0] == '.' && (path[1] == 0 || (path[1] == '.' && path[2] == 0))) {
884 name[0] = '.';
885 name[1] = (path[1] == 0) ? ' ' : '.';
886 for (int i = 2; i < 11; ++i) {
887 name[i] = ' ';
889 name[11] = 0;
890 return -1;
893 // Extract the name part.
894 while (true) {
895 c = (int)(uint8_t)path[pos++];
896 if (c == 0 || c == '.' || c == '/' || c == '\\') {
897 break;
899 if (npos < 8) {
900 name[npos++] = (char)_mfat_canonicalize_char(c);
904 // Space-fill remaining characters of the name part.
905 for (; npos < 8; ++npos) {
906 name[npos] = ' ';
909 // Extract the extension part.
910 if (c == '.') {
911 while (true) {
912 c = (int)(uint8_t)path[pos++];
913 if (c == 0 || c == '/' || c == '\\') {
914 break;
916 if (npos < 11) {
917 name[npos++] = (char)_mfat_canonicalize_char(c);
922 // Space-fill remaining characters of the extension part.
923 for (; npos < 11; ++npos) {
924 name[npos] = ' ';
927 // Zero terminate the string.
928 name[11] = 0;
930 // Was this a directory part of the path (ignore trailing directory separators)?
931 if ((c == '/' || c == '\\') && path[pos] != 0) {
932 // Return the starting position of the next path part.
933 return pos;
936 // Indicate that there are no more path parts by returning -1.
937 return -1;
940 /// @brief Find a file on the given partition.
942 /// If the directory (if part of the path) exists, but the file does not exist in the directory,
943 /// this function will find the first free directory entry slot and set @c exists to false. This is
944 /// useful for creating new files.
945 /// @param part_no The partition number.
946 /// @param path The absolute path to the file.
947 /// @param[out] info Information about the file.
948 /// @param[out] file_type The file type (e.g. dir or regular file).
949 /// @param[out] exists true if the file exists, false if it needs to be created.
950 /// @returns true if the file (or its potential slot) was found.
951 static mfat_bool_t _mfat_find_file(int part_no,
952 const char* path,
953 mfat_file_info_t* info,
954 int* file_type,
955 mfat_bool_t* exists) {
956 mfat_partition_t* part = &s_ctx.partition[part_no];
958 // Start with the root directory cluster/block.
959 mfat_cluster_pos_t cpos;
960 uint32_t blocks_left;
961 if (part->type == MFAT_PART_TYPE_FAT32) {
962 cpos = _mfat_cluster_pos_init(part, part->root_dir_cluster, 0);
963 blocks_left = 0xffffffffU;
964 } else {
965 // We use a fake/tweaked cluster pos for FAT16 root directories.
966 cpos.cluster_no = 0U;
967 cpos.cluster_start_blk = part->root_dir_block;
968 cpos.block_in_cluster = 0U;
969 blocks_left = part->blocks_in_root_dir;
972 // Special case: Is the caller trying to open the root directory?
973 mfat_bool_t is_root_dir = false;
974 if (path[1] == 0 && (path[0] == '/' || path[0] == '\\')) {
975 DBG("Request to open root directory");
976 is_root_dir = true;
979 // Try to find the given path.
980 mfat_cached_block_t* block = NULL;
981 uint8_t* file_entry = NULL;
982 if (!is_root_dir) {
983 // Skip leading slashes.
984 while (*path == '/' || *path == '\\') {
985 ++path;
988 int path_pos = 0;
989 while (path_pos >= 0) {
990 // Extract a directory entry compatible file name.
991 char fname[12];
992 int name_pos = _mfat_canonicalize_fname(&path[path_pos], fname);
993 mfat_bool_t is_parent_dir = (name_pos >= 0);
995 path_pos = is_parent_dir ? path_pos + name_pos : -1;
996 DBGF("Looking for %s: \"%s\"", is_parent_dir ? "parent dir" : "file", fname);
998 // Use an "unlimited" block counter if we're doing a clusterchain lookup.
999 if (cpos.cluster_no != 0U) {
1000 blocks_left = 0xffffffffU;
1003 // Look up the file name in the directory.
1004 mfat_bool_t no_more_entries = false;
1005 for (; file_entry == NULL && !no_more_entries && blocks_left > 0U; --blocks_left) {
1006 // Load the directory table block.
1007 block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1008 if (block == NULL) {
1009 DBGF("Unable to load directory block %" PRIu32, _mfat_cluster_pos_blk_no(&cpos));
1010 return false;
1012 uint8_t* buf = &block->buf[0];
1014 // Loop over all the files in this directory block.
1015 uint8_t* found_entry = NULL;
1016 for (uint32_t offs = 0U; offs < 512U; offs += 32U) {
1017 uint8_t* entry = &buf[offs];
1019 // TODO(m): Look for the first 0xe5-entry too in case we want to create a new file.
1021 // Last entry in the directory structure?
1022 if (entry[0] == 0x00) {
1023 no_more_entries = true;
1024 break;
1027 // Not a file/dir entry?
1028 if (!_mfat_is_valid_shortname_file(entry)) {
1029 continue;
1032 // Is this the file/dir that we are looking for?
1033 if (_mfat_cmpbuf(&entry[0], (const uint8_t*)&fname[0], 11)) {
1034 found_entry = entry;
1035 break;
1039 // Did we have a match.
1040 if (found_entry != NULL) {
1041 uint32_t attr = found_entry[11];
1043 // Descend into directory?
1044 if (is_parent_dir) {
1045 if ((attr & MFAT_ATTR_DIRECTORY) == 0U) {
1046 DBGF("Not a directory: %s", fname);
1047 return false;
1050 // Decode the starting cluster of the child directory entry table.
1051 uint32_t chile_dir_cluster_no =
1052 (_mfat_get_word(&found_entry[20]) << 16) | _mfat_get_word(&found_entry[26]);
1053 cpos = _mfat_cluster_pos_init(part, chile_dir_cluster_no, 0);
1054 blocks_left = 0xffffffffU;
1055 } else {
1056 file_entry = found_entry;
1059 break;
1062 // Go to the next block in the directory.
1063 if (cpos.cluster_no != 0U) {
1064 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1065 return false;
1067 } else {
1068 cpos.block_in_cluster += 1; // FAT16 style linear block access.
1072 // Break loop if we didn't find the file.
1073 if (no_more_entries) {
1074 break;
1079 // Could we neither find the file nor a new directory slot for the file?
1080 if (file_entry == NULL && !is_root_dir) {
1081 return false;
1084 // Define the file properties.
1085 info->part_no = part_no;
1086 if (is_root_dir) {
1087 // Special handling of the root directory (it does not have an entry in any directory block).
1088 info->size = 0U;
1089 info->first_cluster = cpos.cluster_no;
1090 info->dir_entry_block = 0U;
1091 info->dir_entry_offset = 0U;
1092 *file_type = (cpos.cluster_no == 0U) ? MFAT_FILE_TYPE_FAT16ROOTDIR : MFAT_FILE_TYPE_DIR;
1093 *exists = true;
1094 } else {
1095 // For files and non root dirs, we extract file information from the directory block.
1096 info->size = _mfat_get_dword(&file_entry[28]);
1097 info->first_cluster = (_mfat_get_word(&file_entry[20]) << 16) | _mfat_get_word(&file_entry[26]);
1098 info->dir_entry_block = block->blk_no;
1099 info->dir_entry_offset = file_entry - block->buf;
1101 // Does the file exist?
1102 if ((file_entry[0] != 0x00) && (file_entry[0] != 0xe5)) {
1103 *file_type = (file_entry[11] & MFAT_ATTR_DIRECTORY) != 0U ? MFAT_FILE_TYPE_DIR
1104 : MFAT_FILE_TYPE_REGULAR;
1105 *exists = true;
1106 } else {
1107 // Non-existent files must be regular files, since we can't create directories with
1108 // MFAT_O_CREAT.
1109 *file_type = MFAT_FILE_TYPE_REGULAR;
1110 *exists = false;
1114 return true;
1117 #if MFAT_ENABLE_WRITE
1118 static void _mfat_sync_impl() {
1119 for (int j = 0; j < MFAT_NUM_CACHES; ++j) {
1120 mfat_cache_t* cache = &s_ctx.cache[j];
1121 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
1122 mfat_cached_block_t* cb = &cache->block[i];
1123 if (cb->state == MFAT_DIRTY) {
1124 DBGF("Cache: Flushing block %" PRIu32, cb->blk_no);
1125 s_ctx.write((const char*)cb->buf, cb->blk_no, s_ctx.custom);
1126 cb->state = MFAT_VALID;
1131 #endif
1133 static int _mfat_fstat_impl(mfat_file_info_t* info, mfat_stat_t* stat) {
1134 // We can't stat root directories.
1135 if (info->dir_entry_block == 0U) {
1136 return -1;
1139 // Read the directory entry block (should already be in the cache).
1140 mfat_cached_block_t* block = _mfat_read_block(info->dir_entry_block, MFAT_CACHE_DATA);
1141 if (block == NULL) {
1142 return -1;
1145 // Extract the relevant information for this directory entry.
1146 uint8_t* dir_entry = &block->buf[info->dir_entry_offset];
1147 _mfat_dir_entry_to_stat(dir_entry, stat);
1149 return 0;
1152 static int _mfat_stat_impl(const char* path, mfat_stat_t* stat) {
1153 // Find the file in the file system structure.
1154 mfat_bool_t is_dir;
1155 mfat_bool_t exists;
1156 mfat_file_info_t info;
1157 mfat_bool_t ok = _mfat_find_file(s_ctx.active_partition, path, &info, &is_dir, &exists);
1158 if (!ok || !exists) {
1159 DBGF("File not found: %s", path);
1160 return -1;
1163 return _mfat_fstat_impl(&info, stat);
1166 static int _mfat_open_impl(const char* path, int oflag) {
1167 // Find the next free fd.
1168 int fd;
1169 for (fd = 0; fd < MFAT_NUM_FDS; ++fd) {
1170 if (!s_ctx.file[fd].open) {
1171 break;
1174 if (fd >= MFAT_NUM_FDS) {
1175 DBG("No free FD:s left");
1176 return -1;
1178 mfat_file_t* f = &s_ctx.file[fd];
1180 // Find the file in the file system structure.
1181 int file_type;
1182 mfat_bool_t exists;
1183 if (!_mfat_find_file(s_ctx.active_partition, path, &f->info, &file_type, &exists)) {
1184 DBGF("File not found: %s", path);
1185 return -1;
1187 mfat_bool_t is_dir =
1188 (file_type == MFAT_FILE_TYPE_DIR) || (file_type == MFAT_FILE_TYPE_FAT16ROOTDIR);
1190 // Check that we found the correct type.
1191 if (((oflag & MFAT_O_DIRECTORY) != 0) && !is_dir) {
1192 DBGF("Can not open the file as a directory: %s", path);
1193 return -1;
1196 // Handle non-existing files (can only happen for regular files).
1197 if (!exists) {
1198 #if MFAT_ENABLE_WRITE
1199 // Should we create the file?
1200 if ((oflag & MFAT_O_CREAT) != 0U) {
1201 DBG("Creating files is not yet implemented");
1202 return -1;
1204 #endif
1206 DBGF("File does not exist: %s", path);
1207 return -1;
1210 // Initialize the file state.
1211 f->open = true;
1212 f->type = file_type;
1213 f->oflag = oflag;
1214 f->current_cluster = f->info.first_cluster;
1215 f->offset = 0U;
1217 DBGF("Opening file: first_cluster = %" PRIu32 " (block = %" PRIu32 "), size = %" PRIu32
1218 " bytes, dir_blk = %" PRIu32
1219 ", dir_offs "
1220 "= %" PRIu32,
1221 f->info.first_cluster,
1222 _mfat_first_block_of_cluster(&s_ctx.partition[f->info.part_no], f->info.first_cluster),
1223 f->info.size,
1224 f->info.dir_entry_block,
1225 f->info.dir_entry_offset);
1227 return fd;
1230 static int _mfat_close_impl(mfat_file_t* f) {
1231 #if MFAT_ENABLE_WRITE
1232 // For good measure, we flush pending writes when a file is closed (only do this when closing
1233 // files that are open with write permissions).
1234 if ((f->oflag & MFAT_O_WRONLY) != 0) {
1235 _mfat_sync_impl();
1237 #endif
1239 // The file is no longer open. This makes the fd available for future open() requests.
1240 f->open = false;
1242 return 0;
1245 static int64_t _mfat_read_impl(mfat_file_t* f, uint8_t* buf, uint32_t nbyte) {
1246 // Is the file open with read permissions?
1247 if ((f->oflag & MFAT_O_RDONLY) == 0) {
1248 return -1;
1251 // Determine actual size of the operation (clamp to the size of the file).
1252 if (nbyte > (f->info.size - f->offset)) {
1253 nbyte = f->info.size - f->offset;
1254 DBGF("read: Clamped read request to %" PRIu32 " bytes", nbyte);
1257 // Early out if only zero bytes were requested (e.g. if we are at the EOF).
1258 if (nbyte == 0U) {
1259 return 0;
1262 // Start out at the current file offset.
1263 mfat_partition_t* part = &s_ctx.partition[f->info.part_no];
1264 mfat_cluster_pos_t cpos = _mfat_cluster_pos_init_from_file(part, f);
1265 uint32_t bytes_read = 0U;
1267 // Align the head of the operation to a block boundary.
1268 uint32_t block_offset = f->offset % MFAT_BLOCK_SIZE;
1269 if (block_offset != 0U) {
1270 // Use the block cache to get a partial block.
1271 mfat_cached_block_t* block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1272 if (block == NULL) {
1273 DBG("Unable to read block");
1274 return -1;
1277 // Copy the data from the cache to the target buffer.
1278 uint32_t tail_bytes_in_block = MFAT_BLOCK_SIZE - block_offset;
1279 uint32_t bytes_to_copy = _mfat_min(tail_bytes_in_block, nbyte);
1280 memcpy(buf, &block->buf[block_offset], bytes_to_copy);
1281 DBGF("read: Head read of %" PRIu32 " bytes", bytes_to_copy);
1283 buf += bytes_to_copy;
1284 bytes_read += bytes_to_copy;
1286 // Move to the next block if we have read all the bytes of the block.
1287 if (bytes_to_copy == tail_bytes_in_block) {
1288 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1289 return -1;
1294 // Read aligned blocks directly into the target buffer.
1295 while ((nbyte - bytes_read) >= MFAT_BLOCK_SIZE) {
1296 if (_mfat_is_eoc(cpos.cluster_no)) {
1297 DBG("Unexpected cluster access after EOC");
1298 return -1;
1301 DBGF("read: Direct read of %d bytes", MFAT_BLOCK_SIZE);
1302 if (s_ctx.read((char*)buf, _mfat_cluster_pos_blk_no(&cpos), s_ctx.custom) == -1) {
1303 DBG("Unable to read block");
1304 return -1;
1306 buf += MFAT_BLOCK_SIZE;
1307 bytes_read += MFAT_BLOCK_SIZE;
1309 // Move to the next block.
1310 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1311 return -1;
1315 // Handle the tail of the operation (unaligned tail).
1316 if (bytes_read < nbyte) {
1317 if (_mfat_is_eoc(cpos.cluster_no)) {
1318 DBG("Unexpected cluster access after EOC");
1319 return -1;
1322 // Use the block cache to get a partial block.
1323 mfat_cached_block_t* block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1324 if (block == NULL) {
1325 DBG("Unable to read block");
1326 return -1;
1329 // Copy the data from the cache to the target buffer.
1330 uint32_t bytes_to_copy = nbyte - bytes_read;
1331 memcpy(buf, &block->buf[0], bytes_to_copy);
1332 DBGF("read: Tail read of %" PRIu32 " bytes", bytes_to_copy);
1334 bytes_read += bytes_to_copy;
1337 // Update file state.
1338 f->current_cluster = cpos.cluster_no;
1339 f->offset += bytes_read;
1341 return bytes_read;
1344 #if MFAT_ENABLE_WRITE
1345 static int64_t _mfat_write_impl(mfat_file_t* f, const uint8_t* buf, uint32_t nbyte) {
1346 // Is the file open with write permissions?
1347 if ((f->oflag & MFAT_O_WRONLY) == 0) {
1348 return -1;
1351 // TODO(m): Implement me!
1352 (void)buf;
1353 (void)nbyte;
1354 DBG("_mfat_write_impl() - not yet implemented");
1355 return -1;
1357 #endif
1359 static int64_t _mfat_lseek_impl(mfat_file_t* f, int64_t offset, int whence) {
1360 // Calculate the new requested file offset (the arithmetic is done in 64-bit precision in order to
1361 // properly handle overflow).
1362 int64_t target_offset_64;
1363 switch (whence) {
1364 case MFAT_SEEK_SET:
1365 target_offset_64 = offset;
1366 break;
1367 case MFAT_SEEK_END:
1368 target_offset_64 = ((int64_t)f->info.size) + offset;
1369 break;
1370 case MFAT_SEEK_CUR:
1371 target_offset_64 = ((int64_t)f->offset) + offset;
1372 break;
1373 default:
1374 DBGF("Invalid whence: %d", whence);
1375 return -1;
1378 // Sanity check the new offset.
1379 if (target_offset_64 < 0) {
1380 DBG("Seeking to a negative offset is not allowed");
1381 return -1;
1383 if (target_offset_64 > (int64_t)f->info.size) {
1384 // TODO(m): POSIX lseek() allows seeking beyond the end of the file. We currently don't.
1385 DBG("Seeking beyond the end of the file is not allowed");
1386 return -1;
1389 // This type conversion is safe, since we have verified that the value fits in a 32 bit unsigned
1390 // value.
1391 uint32_t target_offset = (uint32_t)target_offset_64;
1393 // Get partition info.
1394 mfat_partition_t* part = &s_ctx.partition[f->info.part_no];
1395 uint32_t bytes_per_cluster = part->blocks_per_cluster * MFAT_BLOCK_SIZE;
1397 // Define the starting point for the cluster search.
1398 uint32_t current_cluster = f->current_cluster;
1399 uint32_t cluster_offset = f->offset - (f->offset % bytes_per_cluster);
1400 if (target_offset < cluster_offset) {
1401 // For reverse seeking we need to start from the beginning of the file since FAT uses singly
1402 // linked lists.
1403 current_cluster = f->info.first_cluster;
1404 cluster_offset = 0;
1407 // Skip along clusters until we find the cluster that contains the requested offset.
1408 while ((target_offset - cluster_offset) >= bytes_per_cluster) {
1409 if (_mfat_is_eoc(current_cluster)) {
1410 DBG("Unexpected cluster access after EOC");
1411 return -1;
1414 // Look up the next cluster.
1415 if (!_mfat_next_cluster(part, &current_cluster)) {
1416 return -1;
1418 cluster_offset += bytes_per_cluster;
1421 // Update the current offset in the file descriptor.
1422 f->offset = target_offset;
1423 f->current_cluster = current_cluster;
1425 return (int64_t)target_offset;
1428 //--------------------------------------------------------------------------------------------------
1429 // Public API functions.
1430 //--------------------------------------------------------------------------------------------------
1432 int mfat_mount(mfat_read_block_fun_t read_fun, mfat_write_block_fun_t write_fun, void* custom) {
1433 #if MFAT_ENABLE_WRITE
1434 if (read_fun == NULL || write_fun == NULL) {
1435 #else
1436 if (read_fun == NULL) {
1437 #endif
1438 DBG("Bad function pointers");
1439 return -1;
1442 // Clear the context state.
1443 memset(&s_ctx, 0, sizeof(mfat_ctx_t));
1444 s_ctx.read = read_fun;
1445 #if MFAT_ENABLE_WRITE
1446 s_ctx.write = write_fun;
1447 #else
1448 (void)write_fun;
1449 #endif
1450 s_ctx.custom = custom;
1451 s_ctx.active_partition = -1;
1453 #if MFAT_NUM_CACHED_BLOCKS > 1
1454 // Initialize the block cache priority queues.
1455 for (int j = 0; j < MFAT_NUM_CACHES; ++j) {
1456 mfat_cache_t* cache = &s_ctx.cache[j];
1457 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
1458 // Assign initial items to the priority queue.
1459 cache->pri[i] = i;
1462 #endif
1464 // Read the partition tables.
1465 if (!_mfat_decode_partition_tables()) {
1466 return -1;
1469 // Find the first bootable partition. If no bootable partition is found, pick the first supported
1470 // partition.
1471 int first_boot_partition = -1;
1472 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
1473 if (s_ctx.partition[i].type != MFAT_PART_TYPE_UNKNOWN) {
1474 if (s_ctx.partition[i].boot && first_boot_partition < 0) {
1475 first_boot_partition = i;
1476 s_ctx.active_partition = i;
1477 } else if (s_ctx.active_partition < 0) {
1478 s_ctx.active_partition = i;
1482 if (s_ctx.active_partition < 0) {
1483 return -1;
1485 DBGF("Selected partition %d", s_ctx.active_partition);
1487 // MFAT is now initizlied.
1488 DBG("Successfully initialized");
1489 s_ctx.initialized = true;
1491 return 0;
1494 void mfat_unmount(void) {
1495 #if MFAT_ENABLE_WRITE
1496 // Flush any pending writes.
1497 _mfat_sync_impl();
1498 #endif
1499 s_ctx.initialized = false;
1502 int mfat_select_partition(int partition_no) {
1503 if (!s_ctx.initialized) {
1504 DBG("Not initialized");
1505 return -1;
1507 if (partition_no < 0 || partition_no >= MFAT_NUM_PARTITIONS) {
1508 DBGF("Bad partition number: %d", partition_no);
1509 return -1;
1511 if (s_ctx.partition[partition_no].type == MFAT_PART_TYPE_UNKNOWN) {
1512 DBG("Unsupported partition type");
1513 return -1;
1516 s_ctx.active_partition = partition_no;
1518 return 0;
1521 void mfat_sync(void) {
1522 #if MFAT_ENABLE_WRITE
1523 if (!s_ctx.initialized) {
1524 DBG("Not initialized");
1525 return;
1528 _mfat_sync_impl();
1529 #endif
1532 int mfat_fstat(int fd, mfat_stat_t* stat) {
1533 if (!s_ctx.initialized) {
1534 DBG("Not initialized");
1535 return -1;
1538 mfat_file_t* f = _mfat_fd_to_file(fd);
1539 if (f == NULL) {
1540 return -1;
1543 return _mfat_fstat_impl(&f->info, stat);
1546 int mfat_stat(const char* path, mfat_stat_t* stat) {
1547 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1548 DBG("Not initialized");
1549 return -1;
1551 if (path == NULL || stat == NULL) {
1552 return -1;
1555 return _mfat_stat_impl(path, stat);
1558 int mfat_open(const char* path, int oflag) {
1559 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1560 DBG("Not initialized");
1561 return -1;
1563 if (path == NULL || ((oflag & MFAT_O_RDWR) == 0)) {
1564 return -1;
1567 return _mfat_open_impl(path, oflag);
1570 int mfat_close(int fd) {
1571 if (!s_ctx.initialized) {
1572 DBG("Not initialized");
1573 return -1;
1576 mfat_file_t* f = _mfat_fd_to_file(fd);
1577 if (f == NULL) {
1578 return -1;
1581 return _mfat_close_impl(f);
1584 int64_t mfat_read(int fd, void* buf, uint32_t nbyte) {
1585 if (!s_ctx.initialized) {
1586 DBG("Not initialized");
1587 return -1;
1590 mfat_file_t* f = _mfat_fd_to_file(fd);
1591 if (f == NULL || f->type != MFAT_FILE_TYPE_REGULAR) {
1592 return -1;
1595 return _mfat_read_impl(f, (uint8_t*)buf, nbyte);
1598 int64_t mfat_write(int fd, const void* buf, uint32_t nbyte) {
1599 #if MFAT_ENABLE_WRITE
1600 if (!s_ctx.initialized) {
1601 DBG("Not initialized");
1602 return -1;
1605 mfat_file_t* f = _mfat_fd_to_file(fd);
1606 if (f == NULL || f->type != MFAT_FILE_TYPE_REGULAR) {
1607 return -1;
1610 return _mfat_write_impl(f, (const uint8_t*)buf, nbyte);
1611 #else
1612 return -1;
1613 #endif
1616 int64_t mfat_lseek(int fd, int64_t offset, int whence) {
1617 if (!s_ctx.initialized) {
1618 DBG("Not initialized");
1619 return -1;
1622 mfat_file_t* f = _mfat_fd_to_file(fd);
1623 if (f == NULL || f->type != MFAT_FILE_TYPE_REGULAR) {
1624 return -1;
1627 return _mfat_lseek_impl(f, offset, whence);