Fix handling of . and ..
[mfat.git] / mfat.c
blobc3b276b283338cccc3b4f763aa8c2d3db7413388
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 open directories.
64 #ifndef MFAT_NUM_DIRS
65 #define MFAT_NUM_DIRS 2
66 #endif
68 // Maximum number of partitions to support.
69 #ifndef MFAT_NUM_PARTITIONS
70 #define MFAT_NUM_PARTITIONS 4
71 #endif
73 //--------------------------------------------------------------------------------------------------
74 // Debugging macros.
75 //--------------------------------------------------------------------------------------------------
77 #if MFAT_ENABLE_DEBUG
78 #include <stdio.h>
79 #define DBG(_str) (void)fprintf(stderr, "[MFAT] " _str "\n")
80 #define DBGF(_fmt, ...) (void)fprintf(stderr, "[MFAT] " _fmt "\n", __VA_ARGS__)
81 #else
82 #define DBG(_str)
83 #define DBGF(_fmt, ...)
84 #endif // MFAT_ENABLE_DEBUG
86 //--------------------------------------------------------------------------------------------------
87 // FAT definitions (used for encoding/decoding).
88 //--------------------------------------------------------------------------------------------------
90 // Known partition ID:s.
91 #define MFAT_PART_ID_NONE 0x00
92 #define MFAT_PART_ID_FAT16_LT32GB 0x04
93 #define MFAT_PART_ID_FAT16_GT32GB 0x06
94 #define MFAT_PART_ID_FAT32 0x0b
95 #define MFAT_PART_ID_FAT32_LBA 0x0c
96 #define MFAT_PART_ID_FAT16_GT32GB_LBA 0x0e
98 // File attribute flags.
99 #define MFAT_ATTR_READ_ONLY 0x01
100 #define MFAT_ATTR_HIDDEN 0x02
101 #define MFAT_ATTR_SYSTEM 0x04
102 #define MFAT_ATTR_VOLUME_ID 0x08
103 #define MFAT_ATTR_DIRECTORY 0x10
104 #define MFAT_ATTR_ARCHIVE 0x20
105 #define MFAT_ATTR_LONG_NAME 0x0f
107 //--------------------------------------------------------------------------------------------------
108 // Private types and constants.
109 //--------------------------------------------------------------------------------------------------
111 // A custom boolean type (stdbool is byte-sized which is unnecessarily performance costly).
112 typedef int mfat_bool_t;
113 #define false 0
114 #define true 1
116 // mfat_cached_block_t::state
117 #define MFAT_INVALID 0
118 #define MFAT_VALID 1
119 #define MFAT_DIRTY 2
121 // Different types of caches (we keep independent block types in different caches).
122 #define MFAT_CACHE_DATA 0
123 #define MFAT_CACHE_FAT 1
124 #define MFAT_NUM_CACHES 2
126 // mfat_partition_t::type
127 #define MFAT_PART_TYPE_UNKNOWN 0
128 #define MFAT_PART_TYPE_FAT_UNDECIDED 1 // A FAT partion of yet unknown type (FAT16 or FAT32).
129 #define MFAT_PART_TYPE_FAT16 2
130 #define MFAT_PART_TYPE_FAT32 3
132 // mfat_file_t::type
133 #define MFAT_FILE_TYPE_REGULAR 0 // Regular file.
134 #define MFAT_FILE_TYPE_DIR 1 // A directory.
135 #define MFAT_FILE_TYPE_FAT16ROOTDIR 2 // A FAT16 root directory (which is special).
137 // A collection of variables for keeping track of the current cluster & block position, e.g. during
138 // read/write operations.
139 typedef struct {
140 uint32_t cluster_no; ///< The cluster number.
141 uint32_t block_in_cluster; ///< The block offset within the cluster (0..blocks_per_cluster-1).
142 uint32_t cluster_start_blk; ///< Absolute block number of the first block of the cluster.
143 } mfat_cluster_pos_t;
145 typedef struct {
146 uint32_t type;
147 uint32_t first_block;
148 uint32_t num_blocks;
149 uint32_t blocks_per_cluster;
150 #if MFAT_ENABLE_WRITE
151 uint32_t num_clusters;
152 #endif
153 uint32_t blocks_per_fat;
154 uint32_t num_fats;
155 uint32_t num_reserved_blocks;
156 uint32_t root_dir_block; // Used for FAT16.
157 uint32_t blocks_in_root_dir; // Used for FAT16 (zero for FAT32).
158 uint32_t root_dir_cluster; // Used for FAT32.
159 uint32_t first_data_block;
160 mfat_bool_t boot;
161 } mfat_partition_t;
163 // Static information about a file, as specified in the file system.
164 typedef struct {
165 int part_no; // Partition that this file is located on.
166 uint32_t size; // Total size of file, in bytes.
167 uint32_t first_cluster; // Starting cluster for the file.
168 uint32_t dir_entry_block; // Block number for the directory entry of this file.
169 uint32_t dir_entry_offset; // Offset (in bytes) into the directory entry block.
170 } mfat_file_info_t;
172 // File handle, corresponding to a file descriptor (fd).
173 typedef struct {
174 mfat_bool_t open; // Is the file open?
175 int type; // File type (e.g. MFAT_FILE_TYPE_REGULAR or MFAT_FILE_TYPE_DIR).
176 int oflag; // Flags used when opening the file.
177 uint32_t offset; // Current byte offset relative to the file start (seek offset).
178 uint32_t current_cluster; // Current cluster (representing the current seek offset)
179 mfat_file_info_t info;
180 } mfat_file_t;
182 // Forward declared in mfat.h, refered to as the type mfat_dir_t.
183 struct mfat_dir_struct {
184 mfat_file_t* file; // File corresponding to this directory (NULL if not open).
185 mfat_dirent_t dirent; // The current dirent (as returned by readdir()).
186 mfat_cluster_pos_t cpos; // Cluster position.
187 uint32_t blocks_left; // Blocks left to read (only used for FAT16 root dirs).
188 uint32_t block_offset; // Offset relative to the block start.
189 int items_left; // HACK!!!!
192 typedef struct {
193 int state;
194 uint32_t blk_no;
195 uint8_t buf[MFAT_BLOCK_SIZE];
196 } mfat_cached_block_t;
198 typedef struct {
199 mfat_cached_block_t block[MFAT_NUM_CACHED_BLOCKS];
200 #if MFAT_NUM_CACHED_BLOCKS > 1
201 // This is a priority queue: The last item in the queue is an index to the
202 // least recently used cached block item.
203 int pri[MFAT_NUM_CACHED_BLOCKS];
204 #endif
205 } mfat_cache_t;
207 typedef struct {
208 mfat_bool_t initialized;
209 int active_partition;
210 mfat_read_block_fun_t read;
211 #if MFAT_ENABLE_WRITE
212 mfat_write_block_fun_t write;
213 #endif
214 void* custom;
215 mfat_partition_t partition[MFAT_NUM_PARTITIONS];
216 mfat_file_t file[MFAT_NUM_FDS];
217 mfat_dir_t dir[MFAT_NUM_DIRS];
218 mfat_cache_t cache[MFAT_NUM_CACHES];
219 } mfat_ctx_t;
221 // Statically allocated state.
222 static mfat_ctx_t s_ctx;
224 //--------------------------------------------------------------------------------------------------
225 // Private functions.
226 //--------------------------------------------------------------------------------------------------
228 static inline uint32_t _mfat_min(uint32_t a, uint32_t b) {
229 return (a < b) ? a : b;
232 static uint32_t _mfat_get_word(const uint8_t* buf) {
233 return ((uint32_t)buf[0]) | (((uint32_t)buf[1]) << 8);
236 static uint32_t _mfat_get_dword(const uint8_t* buf) {
237 return ((uint32_t)buf[0]) | (((uint32_t)buf[1]) << 8) | (((uint32_t)buf[2]) << 16) |
238 (((uint32_t)buf[3]) << 24);
241 static mfat_bool_t _mfat_cmpbuf(const uint8_t* a, const uint8_t* b, const uint32_t nbyte) {
242 for (uint32_t i = 0; i < nbyte; ++i) {
243 if (a[i] != b[i]) {
244 return false;
247 return true;
250 #if MFAT_ENABLE_MBR
251 static mfat_bool_t _mfat_is_fat_part_id(uint32_t id) {
252 switch (id) {
253 case MFAT_PART_ID_FAT16_LT32GB:
254 case MFAT_PART_ID_FAT16_GT32GB:
255 case MFAT_PART_ID_FAT16_GT32GB_LBA:
256 case MFAT_PART_ID_FAT32:
257 case MFAT_PART_ID_FAT32_LBA:
258 return true;
259 default:
260 return false;
263 #endif // MFAT_ENABLE_MBR
265 #if MFAT_ENABLE_GPT
266 static mfat_bool_t _mfat_is_fat_part_guid(const uint8_t* guid) {
267 // Windows Basic data partition GUID = EBD0A0A2-B9E5-4433-87C0-68B6B72699C7
268 static const uint8_t BDP_GUID[] = {0xa2,
269 0xa0,
270 0xd0,
271 0xeb,
272 0xe5,
273 0xb9,
274 0x33,
275 0x44,
276 0x87,
277 0xc0,
278 0x68,
279 0xb6,
280 0xb7,
281 0x26,
282 0x99,
283 0xc7};
284 if (_mfat_cmpbuf(guid, &BDP_GUID[0], sizeof(BDP_GUID))) {
285 // TODO(m): These may also be NTFS partitions. How to detect?
286 return true;
288 return false;
290 #endif // MFAT_ENABLE_GPT
292 static mfat_bool_t _mfat_is_valid_bpb(const uint8_t* bpb_buf) {
293 // Check that the BPB signature is there.
294 if (bpb_buf[510] != 0x55U || bpb_buf[511] != 0xaaU) {
295 DBGF("\t\tInvalid BPB signature: [%02x,%02x]", bpb_buf[510], bpb_buf[511]);
296 return false;
299 // Check for a valid jump instruction (first three bytes).
300 if (bpb_buf[0] != 0xe9 && !(bpb_buf[0] == 0xeb && bpb_buf[2] == 0x90)) {
301 DBGF("\t\tInvalid BPB jump code: [%02x,%02x,%02x]", bpb_buf[0], bpb_buf[1], bpb_buf[2]);
302 return false;
305 // Check for valid bytes per sector.
306 uint32_t bps = _mfat_get_word(&bpb_buf[11]);
307 if (bps != 512 && bps != 1024 && bps != 2048 && bps != 4096) {
308 DBGF("\t\tInvalid BPB bytes per sector: %" PRIu32, bps);
309 return false;
312 return true;
315 static mfat_bool_t _mfat_is_valid_shortname_file(const uint8_t* dir_entry) {
316 const uint8_t attr = dir_entry[11];
317 if (((attr & MFAT_ATTR_LONG_NAME) == MFAT_ATTR_LONG_NAME) ||
318 ((attr & MFAT_ATTR_VOLUME_ID) == MFAT_ATTR_VOLUME_ID)) {
319 return false;
321 return true;
324 static mfat_cached_block_t* _mfat_get_cached_block(uint32_t blk_no, int cache_type) {
325 // Pick the relevant cache.
326 mfat_cache_t* cache = &s_ctx.cache[cache_type];
328 #if MFAT_NUM_CACHED_BLOCKS > 1
329 // By default, pick the last (least recently used) item in the pirority queue...
330 int item_id = cache->pri[MFAT_NUM_CACHED_BLOCKS - 1];
332 // ...but override it if we have a cache hit.
333 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
334 mfat_cached_block_t* cb = &cache->block[i];
335 if (cb->state != MFAT_INVALID && cb->blk_no == blk_no) {
336 item_id = i;
337 break;
341 // Move the selected item to the front of the priority queue (i.e. it's the most recently used
342 // cache item).
344 int prev_id = item_id;
345 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
346 int this_id = cache->pri[i];
347 cache->pri[i] = prev_id;
348 if (this_id == item_id) {
349 break;
351 prev_id = this_id;
355 mfat_cached_block_t* cached_block = &cache->block[item_id];
356 #else
357 mfat_cached_block_t* cached_block = &cache->block[0];
358 #endif
360 // Reassign the cached block to the requested block number (if necessary).
361 if (cached_block->blk_no != blk_no) {
362 #if MFAT_ENABLE_DEBUG
363 if (cached_block->state != MFAT_INVALID) {
364 DBGF("Cache %d: Evicting block %" PRIu32 " in favor of block %" PRIu32,
365 cache_type,
366 cached_block->blk_no,
367 blk_no);
369 #endif
371 #if MFAT_ENABLE_WRITE
372 // Flush the block?
373 if (cached_block->state == MFAT_DIRTY) {
374 DBGF("Cache %d: Flushing evicted block %" PRIu32, cache_type, cached_block->blk_no);
375 if (s_ctx.write((const char*)cached_block->buf, cached_block->blk_no, s_ctx.custom) == -1) {
376 // FATAL: We can't recover from here... :-(
377 DBGF("Cache %d: Failed to flush the block", cache_type);
378 return NULL;
381 #endif
383 // Set the new block ID.
384 cached_block->blk_no = blk_no;
386 // The contents of the buffer is now invalid.
387 cached_block->state = MFAT_INVALID;
390 return cached_block;
393 static mfat_cached_block_t* _mfat_read_block(uint32_t block_no, int cache_type) {
394 // First query the cache.
395 mfat_cached_block_t* block = _mfat_get_cached_block(block_no, cache_type);
396 if (block == NULL) {
397 return NULL;
400 // If necessary, read the block from storage.
401 if (block->state == MFAT_INVALID) {
402 if (s_ctx.read((char*)block->buf, block_no, s_ctx.custom) == -1) {
403 return NULL;
405 block->state = MFAT_VALID;
408 return block;
411 // Helper function for finding the next cluster in a cluster chain.
412 static mfat_bool_t _mfat_next_cluster(const mfat_partition_t* part, uint32_t* cluster) {
413 const uint32_t fat_entry_size = (part->type == MFAT_PART_TYPE_FAT32) ? 4 : 2;
415 uint32_t fat_offset = fat_entry_size * (*cluster);
416 uint32_t fat_block =
417 part->first_block + part->num_reserved_blocks + (fat_offset / MFAT_BLOCK_SIZE);
418 uint32_t fat_block_offset = fat_offset % MFAT_BLOCK_SIZE;
420 // For FAT copy no. N (0..num_fats-1):
421 // fat_block += N * part->blocks_per_fat
423 // Read the FAT block into a cached buffer.
424 mfat_cached_block_t* block = _mfat_read_block(fat_block, MFAT_CACHE_FAT);
425 if (block == NULL) {
426 DBGF("Failed to read the FAT block %" PRIu32, fat_block);
427 return false;
429 uint8_t* buf = &block->buf[0];
431 // Get the value for this cluster from the FAT.
432 uint32_t next_cluster;
433 if (part->type == MFAT_PART_TYPE_FAT32) {
434 // For FAT32 we mask off upper 4 bits, as the cluster number is 28 bits.
435 next_cluster = _mfat_get_dword(&buf[fat_block_offset]) & 0x0fffffffU;
436 } else {
437 next_cluster = _mfat_get_word(&buf[fat_block_offset]);
438 if (next_cluster >= 0xfff7U) {
439 // Convert FAT16 special codes (BAD & EOC) to FAT32 codes.
440 next_cluster |= 0x0fff0000U;
444 // This is a sanity check (failure indicates a corrupt filesystem). We should really do this check
445 // BEFORE accessing the cluster instead.
446 // We do not expect to see:
447 // 0x00000000 - free cluster
448 // 0x0ffffff7 - BAD cluster
449 if (next_cluster == 0U || next_cluster == 0x0ffffff7U) {
450 DBGF("Unexpected next cluster: 0x%08" PRIx32, next_cluster);
451 return false;
454 DBGF("Next cluster: %" PRIu32 " (0x%08" PRIx32 ")", next_cluster, next_cluster);
456 // Return the next cluster number.
457 *cluster = next_cluster;
459 return true;
462 // Helper function for finding the first block of a cluster.
463 static uint32_t _mfat_first_block_of_cluster(const mfat_partition_t* part, uint32_t cluster) {
464 return part->first_data_block + ((cluster - 2U) * part->blocks_per_cluster);
467 // Initialize a cluster pos object to the current cluster & offset (e.g. for a file).
468 static mfat_cluster_pos_t _mfat_cluster_pos_init(const mfat_partition_t* part,
469 const uint32_t cluster_no,
470 const uint32_t offset) {
471 mfat_cluster_pos_t cpos;
472 cpos.cluster_no = cluster_no;
473 cpos.block_in_cluster = (offset % (part->blocks_per_cluster * MFAT_BLOCK_SIZE)) / MFAT_BLOCK_SIZE;
474 cpos.cluster_start_blk = _mfat_first_block_of_cluster(part, cluster_no);
475 return cpos;
478 // Initialize a cluster pos object to the current file offset of the specified file.
479 static mfat_cluster_pos_t _mfat_cluster_pos_init_from_file(const mfat_partition_t* part,
480 const mfat_file_t* f) {
481 return _mfat_cluster_pos_init(part, f->current_cluster, f->offset);
484 // Advance a cluster pos by one block.
485 static mfat_bool_t _mfat_cluster_pos_advance(mfat_cluster_pos_t* cpos,
486 const mfat_partition_t* part) {
487 ++cpos->block_in_cluster;
488 if (cpos->block_in_cluster == part->blocks_per_cluster) {
489 if (!_mfat_next_cluster(part, &cpos->cluster_no)) {
490 return false;
492 cpos->cluster_start_blk = _mfat_first_block_of_cluster(part, cpos->cluster_no);
493 cpos->block_in_cluster = 0;
495 return true;
498 // Get the current absolute block of a cluster pos object.
499 static uint32_t _mfat_cluster_pos_blk_no(const mfat_cluster_pos_t* cpos) {
500 return cpos->cluster_start_blk + cpos->block_in_cluster;
503 // Check for EOC (End of clusterchain). NOTE: EOC cluster is included in file.
504 static mfat_bool_t _mfat_is_eoc(const uint32_t cluster) {
505 return cluster >= 0x0ffffff8U;
508 #if MFAT_ENABLE_GPT
509 static mfat_bool_t _mfat_decode_gpt(void) {
510 // Read the primary GUID Partition Table (GPT) header, located at block 1.
511 mfat_cached_block_t* block = _mfat_read_block(1U, MFAT_CACHE_DATA);
512 if (block == NULL) {
513 DBG("Failed to read the GPT");
514 return false;
516 uint8_t* buf = &block->buf[0];
518 // Is this in fact an GPT header?
519 // TODO(m): We could do more validation (CRC etc).
520 static const uint8_t gpt_sig[] = {0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54};
521 if (!_mfat_cmpbuf(&buf[0], &gpt_sig[0], sizeof(gpt_sig))) {
522 DBG("Not a valid GPT signature");
523 return false;
526 // Get the start LBA of the the partition entries.
527 // Note: This is actually a 64-bit value, but we don't support such large media anyway, and by
528 // reading it out as a 32-bit little endian number we get the correct value IF the upper 32
529 // bits are zero. This should be OK as the value should always be 0x0000000000000002 for the
530 // primary copy, which we are interested in.
531 uint32_t entries_block = _mfat_get_dword(&buf[72]);
533 // Get the number of partition entries.
534 uint32_t num_entries = _mfat_get_dword(&buf[80]);
536 // Get the size of each partition entry, in bytes.
537 uint32_t entry_size = _mfat_get_dword(&buf[84]);
539 DBGF("GPT header: entries_block=%" PRIu32 ", num_entries=%" PRIu32 ", entry_size=%" PRIu32,
540 entries_block,
541 num_entries,
542 entry_size);
544 uint32_t entry_offs = 0;
545 for (uint32_t i = 0; i < num_entries && i < MFAT_NUM_PARTITIONS; ++i) {
546 // Read the next block of the partition entry array if necessary.
547 if ((entry_offs % MFAT_BLOCK_SIZE) == 0) {
548 block = _mfat_read_block(entries_block, MFAT_CACHE_DATA);
549 if (block == NULL) {
550 DBGF("Failed to read the GPT partition entry array at block %" PRIu32, entries_block);
551 return false;
553 buf = &block->buf[0];
555 ++entries_block;
556 entry_offs = 0;
559 // Decode the partition entry.
560 uint8_t* entry = &buf[entry_offs];
561 mfat_partition_t* part = &s_ctx.partition[i];
563 // Get the starting block of the partition (again, 64-bit value that we read as a 32-bit
564 // value).
565 part->first_block = _mfat_get_dword(&entry[32]);
567 // Check if the partition is bootable (bit ).
568 part->boot = ((entry[48] & 0x04) != 0);
570 // Is this a potential FAT partition?
571 if (_mfat_is_fat_part_guid(&entry[0])) {
572 // The actual FAT type is determined later.
573 part->type = MFAT_PART_TYPE_FAT_UNDECIDED;
576 // Note: We print GUIDs in byte order, as we expect them in our signatures, NOT in GUID
577 // mixed endian order.
578 DBGF("GPT entry %" PRIu32 ": first_block = %" PRIu32 ", GUID = %08" PRIx32 "%08" PRIx32
579 "%08" PRIx32 "%08" PRIx32 ", FAT = %s",
581 part->first_block,
582 _mfat_get_dword(&entry[12]),
583 _mfat_get_dword(&entry[8]),
584 _mfat_get_dword(&entry[4]),
585 _mfat_get_dword(&entry[0]),
586 part->type != MFAT_PART_TYPE_UNKNOWN ? "Yes" : "No");
588 entry_offs += entry_size;
591 return true;
593 #endif // MFAT_ENABLE_GPT
595 #if MFAT_ENABLE_MBR
596 static mfat_bool_t _mfat_decode_mbr(void) {
597 mfat_cached_block_t* block = _mfat_read_block(0U, MFAT_CACHE_DATA);
598 if (block == NULL) {
599 DBG("Failed to read the MBR");
600 return false;
602 uint8_t* buf = &block->buf[0];
604 // Is this an MBR block (can also be a partition boot record)?
605 mfat_bool_t found_valid_mbr = false;
606 if (buf[510] == 0x55U && buf[511] == 0xaaU) {
607 // Parse each partition entry.
608 for (int i = 0; i < 4 && i < MFAT_NUM_PARTITIONS; ++i) {
609 uint8_t* entry = &buf[446U + 16U * (uint32_t)i];
610 mfat_partition_t* part = &s_ctx.partition[i];
612 // Current state of partition (00h=Inactive, 80h=Active).
613 part->boot = ((entry[0] & 0x80U) != 0);
615 // Is this a FAT partition.
616 if (_mfat_is_fat_part_id(entry[4])) {
617 // The actual FAT type is determined later.
618 part->type = MFAT_PART_TYPE_FAT_UNDECIDED;
619 found_valid_mbr = true;
622 // Get the beginning of the partition (LBA).
623 part->first_block = _mfat_get_dword(&entry[8]);
625 } else {
626 DBGF("Not a valid MBR signature: [%02x,%02x]", buf[510], buf[511]);
629 return found_valid_mbr;
631 #endif // MFAT_ENABLE_MBR
633 static void _mfat_decode_tableless(void) {
634 // Some storage media are formatted without an MBR or GPT. If so, there is only a single volume
635 // and the first block is the BPB (BIOS Parameter Block) of that "partition". We initially guess
636 // that this is the case, and let the partition decoding logic assert if this was a good guess.
637 DBG("Assuming that the storage medium does not have a partition table");
639 // Clear all partitions (their values are potentially garbage).
640 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
641 memset(&s_ctx.partition[i], 0, sizeof(mfat_partition_t));
644 // Guess that the first partition is FAT (we'll detect the actual type later).
645 // Note: The "first_block" field is cleared to zero in the previous memset, so the first
646 // partition starts at the first block, which is what we intend.
647 s_ctx.partition[0].type = MFAT_PART_TYPE_FAT_UNDECIDED;
650 static mfat_bool_t _mfat_decode_partition_tables(void) {
651 mfat_bool_t found_partition_table = false;
653 #if MFAT_ENABLE_GPT
654 // 1: Try to read the GUID Partition Table (GPT).
655 if (!found_partition_table) {
656 found_partition_table = _mfat_decode_gpt();
658 #endif
660 #if MFAT_ENABLE_MBR
661 // 2: Try to read the Master Boot Record (MBR).
662 if (!found_partition_table) {
663 found_partition_table = _mfat_decode_mbr();
665 #endif
667 // 3: Assume that the storage medium does not have a partition table at all.
668 if (!found_partition_table) {
669 _mfat_decode_tableless();
672 // Read and parse the BPB for each FAT partition.
673 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
674 DBGF("Partition %d:", i);
675 mfat_partition_t* part = &s_ctx.partition[i];
677 // Skip unsupported partition types.
678 if (part->type == MFAT_PART_TYPE_UNKNOWN) {
679 DBG("\t\tNot a FAT partition");
680 continue;
683 // Load the BPB (the first block of the partition).
684 mfat_cached_block_t* block = _mfat_read_block(part->first_block, MFAT_CACHE_DATA);
685 if (block == NULL) {
686 DBG("\t\tFailed to read the BPB");
687 return false;
689 uint8_t* buf = &block->buf[0];
691 if (!_mfat_is_valid_bpb(buf)) {
692 DBG("\t\tPartition does not appear to have a valid BPB");
693 part->type = MFAT_PART_TYPE_UNKNOWN;
694 continue;
697 // Decode useful metrics from the BPB (these are used in block number arithmetic etc).
698 uint32_t bytes_per_block = _mfat_get_word(&buf[11]);
699 part->blocks_per_cluster = buf[13];
700 part->num_reserved_blocks = _mfat_get_word(&buf[14]);
701 part->num_fats = buf[16];
703 uint32_t tot_sectors_16 = _mfat_get_word(&buf[19]);
704 uint32_t tot_sectors_32 = _mfat_get_dword(&buf[32]);
705 part->num_blocks = (tot_sectors_16 != 0) ? tot_sectors_16 : tot_sectors_32;
708 uint32_t blocks_per_fat_16 = _mfat_get_word(&buf[22]);
709 uint32_t blocks_per_fat_32 = _mfat_get_dword(&buf[36]);
710 part->blocks_per_fat = (blocks_per_fat_16 != 0) ? blocks_per_fat_16 : blocks_per_fat_32;
713 uint32_t num_root_entries = _mfat_get_word(&buf[17]);
714 part->blocks_in_root_dir =
715 ((num_root_entries * 32U) + (MFAT_BLOCK_SIZE - 1U)) / MFAT_BLOCK_SIZE;
718 // Derive useful metrics.
719 part->first_data_block = part->first_block + part->num_reserved_blocks +
720 (part->num_fats * part->blocks_per_fat) + part->blocks_in_root_dir;
722 // Check that the number of bytes per sector is 512.
723 // TODO(m): We could add support for larger block sizes.
724 if (bytes_per_block != MFAT_BLOCK_SIZE) {
725 DBGF("\t\tUnsupported block size: %" PRIu32, bytes_per_block);
726 part->type = MFAT_PART_TYPE_UNKNOWN;
727 continue;
730 // Up until now we have only been guessing the partition FAT type. Now we determine the *actual*
731 // partition type (FAT12, FAT16 or FAT32), using the detection algorithm given in the FAT32 File
732 // System Specification, p.14. The definition is based on the count of clusters as follows:
733 // FAT12: count of clusters < 4085
734 // FAT16: 4085 <= count of clusters < 65525
735 // FAT32: 65525 <= count of clusters
737 uint32_t root_ent_cnt = _mfat_get_word(&buf[17]);
738 uint32_t root_dir_sectors = ((root_ent_cnt * 32) + (MFAT_BLOCK_SIZE - 1)) / MFAT_BLOCK_SIZE;
740 uint32_t data_sectors =
741 part->num_blocks -
742 (part->num_reserved_blocks + (part->num_fats * part->blocks_per_fat) + root_dir_sectors);
744 uint32_t count_of_clusters = data_sectors / part->blocks_per_cluster;
746 #if MFAT_ENABLE_WRITE
747 // We need to know the actual number of clusters when writing files.
748 part->num_clusters = count_of_clusters + 1;
749 #endif
751 // We don't support FAT12.
752 if (count_of_clusters < 4085) {
753 part->type = MFAT_PART_TYPE_UNKNOWN;
754 DBG("\t\tFAT12 is not supported.");
755 continue;
758 if (count_of_clusters < 65525) {
759 part->type = MFAT_PART_TYPE_FAT16;
760 } else {
761 part->type = MFAT_PART_TYPE_FAT32;
765 // Determine the starting block/cluster for the root directory.
766 if (part->type == MFAT_PART_TYPE_FAT16) {
767 part->root_dir_block = part->first_data_block - part->blocks_in_root_dir;
768 } else {
769 part->root_dir_cluster = _mfat_get_dword(&buf[44]);
772 #if MFAT_ENABLE_DEBUG
773 // Print the partition information.
774 DBGF("\t\ttype = %s", part->type == MFAT_PART_TYPE_FAT16 ? "FAT16" : "FAT32");
775 DBGF("\t\tboot = %s", part->boot ? "Yes" : "No");
776 DBGF("\t\tfirst_block = %" PRIu32, part->first_block);
777 DBGF("\t\tbytes_per_block = %" PRIu32, bytes_per_block);
778 DBGF("\t\tnum_blocks = %" PRIu32, part->num_blocks);
779 DBGF("\t\tblocks_per_cluster = %" PRIu32, part->blocks_per_cluster);
780 #if MFAT_ENABLE_WRITE
781 DBGF("\t\tnum_clusters = %" PRIu32, part->num_clusters);
782 #endif
783 DBGF("\t\tblocks_per_fat = %" PRIu32, part->blocks_per_fat);
784 DBGF("\t\tnum_fats = %" PRIu32, part->num_fats);
785 DBGF("\t\tnum_reserved_blocks = %" PRIu32, part->num_reserved_blocks);
786 DBGF("\t\troot_dir_block = %" PRIu32, part->root_dir_block);
787 DBGF("\t\troot_dir_cluster = %" PRIu32, part->root_dir_cluster);
788 DBGF("\t\tblocks_in_root_dir = %" PRIu32, part->blocks_in_root_dir);
789 DBGF("\t\tfirst_data_block = %" PRIu32, part->first_data_block);
791 // Print the extended boot signature.
792 const uint8_t* ex_boot_sig = (part->type == MFAT_PART_TYPE_FAT32) ? &buf[66] : &buf[38];
793 if (ex_boot_sig[0] == 0x29) {
794 uint32_t vol_id = _mfat_get_dword(&ex_boot_sig[1]);
796 char label[12];
797 memcpy(&label[0], &ex_boot_sig[5], 11);
798 label[11] = 0;
800 char fs_type[9];
801 memcpy(&fs_type[0], &ex_boot_sig[16], 8);
802 fs_type[8] = 0;
804 DBGF("\t\tvol_id = %04" PRIx32 ":%04" PRIx32, (vol_id >> 16), vol_id & 0xffffU);
805 DBGF("\t\tlabel = \"%s\"", label);
806 DBGF("\t\tfs_type = \"%s\"", fs_type);
807 } else {
808 DBGF("\t\t(Boot signature N/A - bpb[%d]=0x%02x)", (int)(ex_boot_sig - buf), ex_boot_sig[0]);
810 #endif // MFAT_ENABLE_DEBUG
813 return true;
816 static mfat_file_t* _mfat_fd_to_file(int fd) {
817 if (fd < 0 || fd >= MFAT_NUM_FDS) {
818 DBGF("FD out of range: %d", fd);
819 return NULL;
821 mfat_file_t* f = &s_ctx.file[fd];
822 if (!f->open) {
823 DBGF("File is not open: %d", fd);
824 return NULL;
826 return f;
829 static void _mfat_dir_entry_to_stat(uint8_t* dir_entry, mfat_stat_t* stat) {
830 // Decode file attributes.
831 uint32_t attr = dir_entry[11];
832 uint32_t st_mode =
833 MFAT_S_IRUSR | MFAT_S_IRGRP | MFAT_S_IROTH | MFAT_S_IXUSR | MFAT_S_IXGRP | MFAT_S_IXOTH;
834 if ((attr & MFAT_ATTR_READ_ONLY) == 0) {
835 st_mode |= MFAT_S_IWUSR | MFAT_S_IWGRP | MFAT_S_IWOTH;
837 if ((attr & MFAT_ATTR_DIRECTORY) != 0) {
838 st_mode |= MFAT_S_IFDIR;
839 } else {
840 st_mode |= MFAT_S_IFREG;
842 stat->st_mode = st_mode;
844 // Decode time.
845 uint32_t time = _mfat_get_word(&dir_entry[22]);
846 stat->st_mtim.hour = time >> 11;
847 stat->st_mtim.minute = (time >> 5) & 63;
848 stat->st_mtim.second = (time & 31) * 2;
850 // Decode date.
851 uint32_t date = _mfat_get_word(&dir_entry[24]);
852 stat->st_mtim.year = (date >> 9) + 1980U;
853 stat->st_mtim.month = (date >> 5) & 15;
854 stat->st_mtim.day = date & 31;
856 // Get file size.
857 stat->st_size = _mfat_get_dword(&dir_entry[28]);
860 static int _mfat_canonicalize_char(int c) {
861 // Valid character?
862 if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '$' || c == '%' || c == '-' ||
863 c == '_' || c == '@' || c == '~' || c == '`' || c == '!' || c == '(' || c == ')' ||
864 c == '{' || c == '}' || c == '^' || c == '#' || c == '&') {
865 return c;
868 // Convert lower case to upper case.
869 if (c >= 'a' && c <= 'z') {
870 return c - 'a' + 'A';
873 // Invalid character.
874 return '!';
877 /// @brief Canonicalize a file name.
879 /// Convert the next path part (up until a directory separator or the end of the string) into a
880 /// valid 8.3 short file name as represented in a FAT directory entry (i.e. space padded and without
881 /// the dot). File names that do not fit in an 8.3 representation will be silently truncated.
883 /// Examples:
884 /// - "hello.txt" -> "HELLO TXT"
885 /// - "File.1" -> "FILE 1 "
886 /// - "ALongFileName.json" -> "ALONGFILJSO"
887 /// - "bin/foo.exe" -> "BIN " (followed by "FOO EXE" at the next invocation)
888 /// - "." -> ". "
889 /// - ".." -> ".. "
890 /// @param path A path to a file, possibly includuing directory separators (/ or \).
891 /// @param[out] fname The canonicalized file name.
892 /// @returns the index of the next path part, or -1 if this was the last path part.
893 static int _mfat_canonicalize_fname(const char* path, char name[12]) {
894 int pos = 0;
895 int npos = 0;
896 int c;
898 // Special cases: "." and ".." (unlike other file names, the dots are spelled out in these names).
899 if (path[0] == '.' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) {
900 name[0] = '.';
901 for (int i = 1; i < 11; ++i) {
902 name[i] = ' ';
904 name[11] = 0;
905 return (path[1] == 0) ? -1 : 2;
907 if (path[0] == '.' && path[1] == '.' && (path[2] == 0 || path[2] == '/' || path[2] == '\\')) {
908 name[0] = '.';
909 name[1] = '.';
910 for (int i = 2; i < 11; ++i) {
911 name[i] = ' ';
913 name[11] = 0;
914 return (path[2] == 0) ? -1 : 3;
917 // Extract the name part.
918 while (true) {
919 c = (int)(uint8_t)path[pos++];
920 if (c == 0 || c == '.' || c == '/' || c == '\\') {
921 break;
923 if (npos < 8) {
924 name[npos++] = (char)_mfat_canonicalize_char(c);
928 // Space-fill remaining characters of the name part.
929 for (; npos < 8; ++npos) {
930 name[npos] = ' ';
933 // Extract the extension part.
934 if (c == '.') {
935 while (true) {
936 c = (int)(uint8_t)path[pos++];
937 if (c == 0 || c == '/' || c == '\\') {
938 break;
940 if (npos < 11) {
941 name[npos++] = (char)_mfat_canonicalize_char(c);
946 // Space-fill remaining characters of the extension part.
947 for (; npos < 11; ++npos) {
948 name[npos] = ' ';
951 // Zero terminate the string.
952 name[11] = 0;
954 // Was this a directory part of the path (ignore trailing directory separators)?
955 if ((c == '/' || c == '\\') && path[pos] != 0) {
956 // Return the starting position of the next path part.
957 return pos;
960 // Indicate that there are no more path parts by returning -1.
961 return -1;
964 static void _mfat_make_printable_fname(const uint8_t* canonical_name, char printable_name[13]) {
965 int src_idx = 0;
966 int dst_idx = 0;
968 // Name part.
969 while (src_idx < 8 && canonical_name[src_idx] != ' ') {
970 printable_name[dst_idx++] = canonical_name[src_idx++];
972 src_idx = 8;
974 // Extension part.
975 while (src_idx < 11 && canonical_name[src_idx] != ' ') {
976 if (src_idx == 8) {
977 printable_name[dst_idx++] = '.';
979 printable_name[dst_idx++] = canonical_name[src_idx++];
982 // Zero-terminate.
983 printable_name[dst_idx] = 0;
986 /// @brief Find a file on the given partition.
988 /// If the directory (if part of the path) exists, but the file does not exist in the directory,
989 /// this function will find the first free directory entry slot and set @c exists to false. This is
990 /// useful for creating new files.
991 /// @param part_no The partition number.
992 /// @param path The absolute path to the file.
993 /// @param[out] info Information about the file.
994 /// @param[out] file_type The file type (e.g. dir or regular file).
995 /// @param[out] exists true if the file exists, false if it needs to be created.
996 /// @returns true if the file (or its potential slot) was found.
997 static mfat_bool_t _mfat_find_file(int part_no,
998 const char* path,
999 mfat_file_info_t* info,
1000 int* file_type,
1001 mfat_bool_t* exists) {
1002 mfat_partition_t* part = &s_ctx.partition[part_no];
1004 // Start with the root directory cluster/block.
1005 mfat_cluster_pos_t cpos;
1006 uint32_t blocks_left;
1007 if (part->type == MFAT_PART_TYPE_FAT32) {
1008 cpos = _mfat_cluster_pos_init(part, part->root_dir_cluster, 0);
1009 blocks_left = 0xffffffffU;
1010 } else {
1011 // We use a fake/tweaked cluster pos for FAT16 root directories.
1012 cpos.cluster_no = 0U;
1013 cpos.cluster_start_blk = part->root_dir_block;
1014 cpos.block_in_cluster = 0U;
1015 blocks_left = part->blocks_in_root_dir;
1018 // Special case: Is the caller trying to open the root directory?
1019 mfat_bool_t is_root_dir = false;
1020 if (path[1] == 0 && (path[0] == '/' || path[0] == '\\')) {
1021 DBG("Request to open root directory");
1022 is_root_dir = true;
1025 // Try to find the given path.
1026 mfat_cached_block_t* block = NULL;
1027 uint8_t* file_entry = NULL;
1028 if (!is_root_dir) {
1029 // Skip leading slashes.
1030 while (*path == '/' || *path == '\\') {
1031 ++path;
1034 int path_pos = 0;
1035 while (path_pos >= 0) {
1036 // Extract a directory entry compatible file name.
1037 char fname[12];
1038 int name_pos = _mfat_canonicalize_fname(&path[path_pos], fname);
1039 mfat_bool_t is_parent_dir = (name_pos >= 0);
1041 path_pos = is_parent_dir ? path_pos + name_pos : -1;
1042 DBGF("Looking for %s: \"%s\"", is_parent_dir ? "parent dir" : "file", fname);
1044 // Use an "unlimited" block counter if we're doing a clusterchain lookup.
1045 if (cpos.cluster_no != 0U) {
1046 blocks_left = 0xffffffffU;
1049 // Look up the file name in the directory.
1050 mfat_bool_t no_more_entries = false;
1051 for (; file_entry == NULL && !no_more_entries && blocks_left > 0U; --blocks_left) {
1052 // Load the directory table block.
1053 block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1054 if (block == NULL) {
1055 DBGF("Unable to load directory block %" PRIu32, _mfat_cluster_pos_blk_no(&cpos));
1056 return false;
1058 uint8_t* buf = &block->buf[0];
1060 // Loop over all the files in this directory block.
1061 uint8_t* found_entry = NULL;
1062 for (uint32_t offs = 0U; offs < 512U; offs += 32U) {
1063 uint8_t* entry = &buf[offs];
1065 // TODO(m): Look for the first 0xe5-entry too in case we want to create a new file.
1067 // Last entry in the directory structure?
1068 if (entry[0] == 0x00) {
1069 no_more_entries = true;
1070 break;
1073 // Not a file/dir entry?
1074 if (!_mfat_is_valid_shortname_file(entry)) {
1075 continue;
1078 // Is this the file/dir that we are looking for?
1079 if (_mfat_cmpbuf(&entry[0], (const uint8_t*)&fname[0], 11)) {
1080 found_entry = entry;
1081 break;
1085 // Did we have a match.
1086 if (found_entry != NULL) {
1087 uint32_t attr = found_entry[11];
1089 // Descend into directory?
1090 if (is_parent_dir) {
1091 if ((attr & MFAT_ATTR_DIRECTORY) == 0U) {
1092 DBGF("Not a directory: %s", fname);
1093 return false;
1096 // Decode the starting cluster of the child directory entry table.
1097 uint32_t chile_dir_cluster_no =
1098 (_mfat_get_word(&found_entry[20]) << 16) | _mfat_get_word(&found_entry[26]);
1099 cpos = _mfat_cluster_pos_init(part, chile_dir_cluster_no, 0);
1100 blocks_left = 0xffffffffU;
1101 } else {
1102 file_entry = found_entry;
1105 break;
1108 // Go to the next block in the directory.
1109 if (cpos.cluster_no != 0U) {
1110 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1111 return false;
1113 } else {
1114 cpos.block_in_cluster += 1; // FAT16 style linear block access.
1118 // Break loop if we didn't find the file.
1119 if (no_more_entries) {
1120 break;
1125 // Could we neither find the file nor a new directory slot for the file?
1126 if (file_entry == NULL && !is_root_dir) {
1127 return false;
1130 // Define the file properties.
1131 info->part_no = part_no;
1132 if (is_root_dir) {
1133 // Special handling of the root directory (it does not have an entry in any directory block).
1134 info->size = 0U;
1135 info->first_cluster = cpos.cluster_no;
1136 info->dir_entry_block = 0U;
1137 info->dir_entry_offset = 0U;
1138 *file_type = (cpos.cluster_no == 0U) ? MFAT_FILE_TYPE_FAT16ROOTDIR : MFAT_FILE_TYPE_DIR;
1139 *exists = true;
1140 } else {
1141 // For files and non root dirs, we extract file information from the directory block.
1142 info->size = _mfat_get_dword(&file_entry[28]);
1143 info->first_cluster = (_mfat_get_word(&file_entry[20]) << 16) | _mfat_get_word(&file_entry[26]);
1144 info->dir_entry_block = block->blk_no;
1145 info->dir_entry_offset = file_entry - block->buf;
1147 // Does the file exist?
1148 if ((file_entry[0] != 0x00) && (file_entry[0] != 0xe5)) {
1149 *file_type = (file_entry[11] & MFAT_ATTR_DIRECTORY) != 0U ? MFAT_FILE_TYPE_DIR
1150 : MFAT_FILE_TYPE_REGULAR;
1151 *exists = true;
1152 } else {
1153 // Non-existent files must be regular files, since we can't create directories with
1154 // MFAT_O_CREAT.
1155 *file_type = MFAT_FILE_TYPE_REGULAR;
1156 *exists = false;
1160 return true;
1163 #if MFAT_ENABLE_WRITE
1164 static void _mfat_sync_impl() {
1165 for (int j = 0; j < MFAT_NUM_CACHES; ++j) {
1166 mfat_cache_t* cache = &s_ctx.cache[j];
1167 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
1168 mfat_cached_block_t* cb = &cache->block[i];
1169 if (cb->state == MFAT_DIRTY) {
1170 DBGF("Cache: Flushing block %" PRIu32, cb->blk_no);
1171 s_ctx.write((const char*)cb->buf, cb->blk_no, s_ctx.custom);
1172 cb->state = MFAT_VALID;
1177 #endif
1179 static int _mfat_fstat_impl(mfat_file_info_t* info, mfat_stat_t* stat) {
1180 // We can't stat root directories.
1181 if (info->dir_entry_block == 0U) {
1182 return -1;
1185 // Read the directory entry block (should already be in the cache).
1186 mfat_cached_block_t* block = _mfat_read_block(info->dir_entry_block, MFAT_CACHE_DATA);
1187 if (block == NULL) {
1188 return -1;
1191 // Extract the relevant information for this directory entry.
1192 uint8_t* dir_entry = &block->buf[info->dir_entry_offset];
1193 _mfat_dir_entry_to_stat(dir_entry, stat);
1195 return 0;
1198 static int _mfat_stat_impl(const char* path, mfat_stat_t* stat) {
1199 // Find the file in the file system structure.
1200 mfat_bool_t is_dir;
1201 mfat_bool_t exists;
1202 mfat_file_info_t info;
1203 mfat_bool_t ok = _mfat_find_file(s_ctx.active_partition, path, &info, &is_dir, &exists);
1204 if (!ok || !exists) {
1205 DBGF("File not found: %s", path);
1206 return -1;
1209 return _mfat_fstat_impl(&info, stat);
1212 static int _mfat_open_impl(const char* path, int oflag) {
1213 // Find the next free fd.
1214 int fd;
1215 for (fd = 0; fd < MFAT_NUM_FDS; ++fd) {
1216 if (!s_ctx.file[fd].open) {
1217 break;
1220 if (fd >= MFAT_NUM_FDS) {
1221 DBG("No free FD:s left");
1222 return -1;
1224 mfat_file_t* f = &s_ctx.file[fd];
1226 // Find the file in the file system structure.
1227 int file_type;
1228 mfat_bool_t exists;
1229 if (!_mfat_find_file(s_ctx.active_partition, path, &f->info, &file_type, &exists)) {
1230 DBGF("File not found: %s", path);
1231 return -1;
1233 mfat_bool_t is_dir =
1234 (file_type == MFAT_FILE_TYPE_DIR) || (file_type == MFAT_FILE_TYPE_FAT16ROOTDIR);
1236 // Check that we found the correct type.
1237 if (((oflag & MFAT_O_DIRECTORY) != 0) && !is_dir) {
1238 DBGF("Can not open the file as a directory: %s", path);
1239 return -1;
1242 // Handle non-existing files (can only happen for regular files).
1243 if (!exists) {
1244 #if MFAT_ENABLE_WRITE
1245 // Should we create the file?
1246 if ((oflag & MFAT_O_CREAT) != 0U) {
1247 DBG("Creating files is not yet implemented");
1248 return -1;
1250 #endif
1252 DBGF("File does not exist: %s", path);
1253 return -1;
1256 // Initialize the file state.
1257 f->open = true;
1258 f->type = file_type;
1259 f->oflag = oflag;
1260 f->current_cluster = f->info.first_cluster;
1261 f->offset = 0U;
1263 DBGF("Opening file: first_cluster = %" PRIu32 " (block = %" PRIu32 "), size = %" PRIu32
1264 " bytes, dir_blk = %" PRIu32
1265 ", dir_offs "
1266 "= %" PRIu32,
1267 f->info.first_cluster,
1268 _mfat_first_block_of_cluster(&s_ctx.partition[f->info.part_no], f->info.first_cluster),
1269 f->info.size,
1270 f->info.dir_entry_block,
1271 f->info.dir_entry_offset);
1273 return fd;
1276 static int _mfat_close_impl(mfat_file_t* f) {
1277 #if MFAT_ENABLE_WRITE
1278 // For good measure, we flush pending writes when a file is closed (only do this when closing
1279 // files that are open with write permissions).
1280 if ((f->oflag & MFAT_O_WRONLY) != 0) {
1281 _mfat_sync_impl();
1283 #endif
1285 // The file is no longer open. This makes the fd available for future open() requests.
1286 f->open = false;
1288 return 0;
1291 static int64_t _mfat_read_impl(mfat_file_t* f, uint8_t* buf, uint32_t nbyte) {
1292 // Is the file open with read permissions?
1293 if ((f->oflag & MFAT_O_RDONLY) == 0) {
1294 return -1;
1297 // Determine actual size of the operation (clamp to the size of the file).
1298 if (nbyte > (f->info.size - f->offset)) {
1299 nbyte = f->info.size - f->offset;
1300 DBGF("read: Clamped read request to %" PRIu32 " bytes", nbyte);
1303 // Early out if only zero bytes were requested (e.g. if we are at the EOF).
1304 if (nbyte == 0U) {
1305 return 0;
1308 // Start out at the current file offset.
1309 mfat_partition_t* part = &s_ctx.partition[f->info.part_no];
1310 mfat_cluster_pos_t cpos = _mfat_cluster_pos_init_from_file(part, f);
1311 uint32_t bytes_read = 0U;
1313 // Align the head of the operation to a block boundary.
1314 uint32_t block_offset = f->offset % MFAT_BLOCK_SIZE;
1315 if (block_offset != 0U) {
1316 // Use the block cache to get a partial block.
1317 mfat_cached_block_t* block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1318 if (block == NULL) {
1319 DBG("Unable to read block");
1320 return -1;
1323 // Copy the data from the cache to the target buffer.
1324 uint32_t tail_bytes_in_block = MFAT_BLOCK_SIZE - block_offset;
1325 uint32_t bytes_to_copy = _mfat_min(tail_bytes_in_block, nbyte);
1326 memcpy(buf, &block->buf[block_offset], bytes_to_copy);
1327 DBGF("read: Head read of %" PRIu32 " bytes", bytes_to_copy);
1329 buf += bytes_to_copy;
1330 bytes_read += bytes_to_copy;
1332 // Move to the next block if we have read all the bytes of the block.
1333 if (bytes_to_copy == tail_bytes_in_block) {
1334 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1335 return -1;
1340 // Read aligned blocks directly into the target buffer.
1341 while ((nbyte - bytes_read) >= MFAT_BLOCK_SIZE) {
1342 if (_mfat_is_eoc(cpos.cluster_no)) {
1343 DBG("Unexpected cluster access after EOC");
1344 return -1;
1347 DBGF("read: Direct read of %d bytes", MFAT_BLOCK_SIZE);
1348 if (s_ctx.read((char*)buf, _mfat_cluster_pos_blk_no(&cpos), s_ctx.custom) == -1) {
1349 DBG("Unable to read block");
1350 return -1;
1352 buf += MFAT_BLOCK_SIZE;
1353 bytes_read += MFAT_BLOCK_SIZE;
1355 // Move to the next block.
1356 if (!_mfat_cluster_pos_advance(&cpos, part)) {
1357 return -1;
1361 // Handle the tail of the operation (unaligned tail).
1362 if (bytes_read < nbyte) {
1363 if (_mfat_is_eoc(cpos.cluster_no)) {
1364 DBG("Unexpected cluster access after EOC");
1365 return -1;
1368 // Use the block cache to get a partial block.
1369 mfat_cached_block_t* block = _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos), MFAT_CACHE_DATA);
1370 if (block == NULL) {
1371 DBG("Unable to read block");
1372 return -1;
1375 // Copy the data from the cache to the target buffer.
1376 uint32_t bytes_to_copy = nbyte - bytes_read;
1377 memcpy(buf, &block->buf[0], bytes_to_copy);
1378 DBGF("read: Tail read of %" PRIu32 " bytes", bytes_to_copy);
1380 bytes_read += bytes_to_copy;
1383 // Update file state.
1384 f->current_cluster = cpos.cluster_no;
1385 f->offset += bytes_read;
1387 return bytes_read;
1390 #if MFAT_ENABLE_WRITE
1391 static int64_t _mfat_write_impl(mfat_file_t* f, const uint8_t* buf, uint32_t nbyte) {
1392 // Is the file open with write permissions?
1393 if ((f->oflag & MFAT_O_WRONLY) == 0) {
1394 return -1;
1397 // TODO(m): Implement me!
1398 (void)buf;
1399 (void)nbyte;
1400 DBG("_mfat_write_impl() - not yet implemented");
1401 return -1;
1403 #endif
1405 static int64_t _mfat_lseek_impl(mfat_file_t* f, int64_t offset, int whence) {
1406 // Calculate the new requested file offset (the arithmetic is done in 64-bit precision in order to
1407 // properly handle overflow).
1408 int64_t target_offset_64;
1409 switch (whence) {
1410 case MFAT_SEEK_SET:
1411 target_offset_64 = offset;
1412 break;
1413 case MFAT_SEEK_END:
1414 target_offset_64 = ((int64_t)f->info.size) + offset;
1415 break;
1416 case MFAT_SEEK_CUR:
1417 target_offset_64 = ((int64_t)f->offset) + offset;
1418 break;
1419 default:
1420 DBGF("Invalid whence: %d", whence);
1421 return -1;
1424 // Sanity check the new offset.
1425 if (target_offset_64 < 0) {
1426 DBG("Seeking to a negative offset is not allowed");
1427 return -1;
1429 if (target_offset_64 > (int64_t)f->info.size) {
1430 // TODO(m): POSIX lseek() allows seeking beyond the end of the file. We currently don't.
1431 DBG("Seeking beyond the end of the file is not allowed");
1432 return -1;
1435 // This type conversion is safe, since we have verified that the value fits in a 32 bit unsigned
1436 // value.
1437 uint32_t target_offset = (uint32_t)target_offset_64;
1439 // Get partition info.
1440 mfat_partition_t* part = &s_ctx.partition[f->info.part_no];
1441 uint32_t bytes_per_cluster = part->blocks_per_cluster * MFAT_BLOCK_SIZE;
1443 // Define the starting point for the cluster search.
1444 uint32_t current_cluster = f->current_cluster;
1445 uint32_t cluster_offset = f->offset - (f->offset % bytes_per_cluster);
1446 if (target_offset < cluster_offset) {
1447 // For reverse seeking we need to start from the beginning of the file since FAT uses singly
1448 // linked lists.
1449 current_cluster = f->info.first_cluster;
1450 cluster_offset = 0;
1453 // Skip along clusters until we find the cluster that contains the requested offset.
1454 while ((target_offset - cluster_offset) >= bytes_per_cluster) {
1455 if (_mfat_is_eoc(current_cluster)) {
1456 DBG("Unexpected cluster access after EOC");
1457 return -1;
1460 // Look up the next cluster.
1461 if (!_mfat_next_cluster(part, &current_cluster)) {
1462 return -1;
1464 cluster_offset += bytes_per_cluster;
1467 // Update the current offset in the file descriptor.
1468 f->offset = target_offset;
1469 f->current_cluster = current_cluster;
1471 return (int64_t)target_offset;
1474 static mfat_dir_t* _mfat_opendir_impl(int fd) {
1475 // Find the next free dir object.
1476 int dir_id;
1477 for (dir_id = 0; dir_id < MFAT_NUM_DIRS; ++dir_id) {
1478 if (s_ctx.dir[dir_id].file == NULL) {
1479 break;
1482 if (dir_id >= MFAT_NUM_DIRS) {
1483 DBG("No free dir:s left");
1484 return NULL;
1487 // Initialize the directory stream object.
1488 mfat_dir_t* dirp = &s_ctx.dir[dir_id];
1489 dirp->file = _mfat_fd_to_file(fd);
1490 if (dirp->file == NULL) {
1491 DBG("The dir fd is not an open dir");
1492 return NULL;
1494 mfat_partition_t* part = &s_ctx.partition[dirp->file->info.part_no];
1495 if (dirp->file->type == MFAT_FILE_TYPE_FAT16ROOTDIR) {
1496 // We use a fake/tweaked cluster pos for FAT16 root directories.
1497 dirp->cpos.cluster_no = 0U;
1498 dirp->cpos.cluster_start_blk = part->root_dir_block;
1499 dirp->cpos.block_in_cluster = 0U;
1500 dirp->blocks_left = part->blocks_in_root_dir;
1501 } else {
1502 // We use an "infinite" block count for regular cluster chain dirs.
1503 dirp->cpos = _mfat_cluster_pos_init(part, dirp->file->info.first_cluster, 0);
1504 dirp->blocks_left = 0xffffffffU;
1506 dirp->block_offset = 0U;
1508 // TODO(m): Pointer to first file in directory etc.
1509 dirp->items_left = 5; // HACK!
1511 return dirp;
1514 int _mfat_closedir_impl(mfat_dir_t* dirp) {
1515 if (dirp == NULL) {
1516 return -1;
1518 if (dirp->file == NULL) {
1519 DBG("The dir is already closed");
1520 return -1;
1523 // Close the file.
1524 int result = _mfat_close_impl(dirp->file);
1526 // The dir is no longer open. This makes the dir object available for future opendir() requests.
1527 dirp->file = NULL;
1529 return result;
1532 mfat_dirent_t* _mfat_readdir_impl(mfat_dir_t* dirp) {
1533 // Do we need to advance to the next block in the directory?
1534 if (dirp->block_offset >= 512U) {
1535 if (dirp->file->type == MFAT_FILE_TYPE_FAT16ROOTDIR) {
1536 dirp->cpos.block_in_cluster += 1; // FAT16 style linear block access.
1537 } else {
1538 mfat_partition_t* part = &s_ctx.partition[dirp->file->info.part_no];
1539 if (!_mfat_cluster_pos_advance(&dirp->cpos, part)) {
1540 DBG("readdir: Unable to advance to next cluster.");
1541 return NULL;
1544 dirp->block_offset = 0U;
1547 // Look up the next file name in the directory.
1548 mfat_bool_t found_entry = false;
1549 mfat_bool_t no_more_entries = false;
1550 for (; !found_entry && !no_more_entries && dirp->blocks_left > 0U; --dirp->blocks_left) {
1551 // Load the directory table block.
1552 mfat_cached_block_t* block =
1553 _mfat_read_block(_mfat_cluster_pos_blk_no(&dirp->cpos), MFAT_CACHE_DATA);
1554 if (block == NULL) {
1555 DBGF("Unable to load directory block %" PRIu32, _mfat_cluster_pos_blk_no(&dirp->cpos));
1556 return false;
1558 uint8_t* buf = &block->buf[0];
1560 // Loop over all the files in this directory block.
1561 for (; !found_entry && dirp->block_offset < 512U; dirp->block_offset += 32U) {
1562 uint8_t* entry = &buf[dirp->block_offset];
1564 // Empty entry?
1565 if (entry[0] == 0xe5) {
1566 continue;
1569 // Last entry in the directory structure?
1570 if (entry[0] == 0x00) {
1571 no_more_entries = true;
1572 break;
1575 // A file/dir entry?
1576 if (_mfat_is_valid_shortname_file(entry)) {
1577 // We found the next file.
1578 found_entry = true;
1579 _mfat_make_printable_fname(entry, dirp->dirent.d_name);
1584 if (!found_entry) {
1585 return NULL;
1588 return &dirp->dirent;
1591 //--------------------------------------------------------------------------------------------------
1592 // Public API functions.
1593 //--------------------------------------------------------------------------------------------------
1595 int mfat_mount(mfat_read_block_fun_t read_fun, mfat_write_block_fun_t write_fun, void* custom) {
1596 #if MFAT_ENABLE_WRITE
1597 if (read_fun == NULL || write_fun == NULL) {
1598 #else
1599 if (read_fun == NULL) {
1600 #endif
1601 DBG("Bad function pointers");
1602 return -1;
1605 // Clear the context state.
1606 memset(&s_ctx, 0, sizeof(mfat_ctx_t));
1607 s_ctx.read = read_fun;
1608 #if MFAT_ENABLE_WRITE
1609 s_ctx.write = write_fun;
1610 #else
1611 (void)write_fun;
1612 #endif
1613 s_ctx.custom = custom;
1614 s_ctx.active_partition = -1;
1616 #if MFAT_NUM_CACHED_BLOCKS > 1
1617 // Initialize the block cache priority queues.
1618 for (int j = 0; j < MFAT_NUM_CACHES; ++j) {
1619 mfat_cache_t* cache = &s_ctx.cache[j];
1620 for (int i = 0; i < MFAT_NUM_CACHED_BLOCKS; ++i) {
1621 // Assign initial items to the priority queue.
1622 cache->pri[i] = i;
1625 #endif
1627 // Read the partition tables.
1628 if (!_mfat_decode_partition_tables()) {
1629 return -1;
1632 // Find the first bootable partition. If no bootable partition is found, pick the first supported
1633 // partition.
1634 int first_boot_partition = -1;
1635 for (int i = 0; i < MFAT_NUM_PARTITIONS; ++i) {
1636 if (s_ctx.partition[i].type != MFAT_PART_TYPE_UNKNOWN) {
1637 if (s_ctx.partition[i].boot && first_boot_partition < 0) {
1638 first_boot_partition = i;
1639 s_ctx.active_partition = i;
1640 } else if (s_ctx.active_partition < 0) {
1641 s_ctx.active_partition = i;
1645 if (s_ctx.active_partition < 0) {
1646 return -1;
1648 DBGF("Selected partition %d", s_ctx.active_partition);
1650 // MFAT is now initizlied.
1651 DBG("Successfully initialized");
1652 s_ctx.initialized = true;
1654 return 0;
1657 void mfat_unmount(void) {
1658 #if MFAT_ENABLE_WRITE
1659 // Flush any pending writes.
1660 _mfat_sync_impl();
1661 #endif
1662 s_ctx.initialized = false;
1665 int mfat_select_partition(int partition_no) {
1666 if (!s_ctx.initialized) {
1667 DBG("Not initialized");
1668 return -1;
1670 if (partition_no < 0 || partition_no >= MFAT_NUM_PARTITIONS) {
1671 DBGF("Bad partition number: %d", partition_no);
1672 return -1;
1674 if (s_ctx.partition[partition_no].type == MFAT_PART_TYPE_UNKNOWN) {
1675 DBG("Unsupported partition type");
1676 return -1;
1679 s_ctx.active_partition = partition_no;
1681 return 0;
1684 void mfat_sync(void) {
1685 #if MFAT_ENABLE_WRITE
1686 if (!s_ctx.initialized) {
1687 DBG("Not initialized");
1688 return;
1691 _mfat_sync_impl();
1692 #endif
1695 int mfat_fstat(int fd, mfat_stat_t* stat) {
1696 if (!s_ctx.initialized) {
1697 DBG("Not initialized");
1698 return -1;
1701 mfat_file_t* f = _mfat_fd_to_file(fd);
1702 if (f == NULL) {
1703 return -1;
1706 return _mfat_fstat_impl(&f->info, stat);
1709 int mfat_stat(const char* path, mfat_stat_t* stat) {
1710 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1711 DBG("Not initialized");
1712 return -1;
1714 if (path == NULL || stat == NULL) {
1715 return -1;
1718 return _mfat_stat_impl(path, stat);
1721 int mfat_open(const char* path, int oflag) {
1722 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1723 DBG("Not initialized");
1724 return -1;
1726 if (path == NULL || ((oflag & MFAT_O_RDWR) == 0)) {
1727 return -1;
1730 return _mfat_open_impl(path, oflag);
1733 int mfat_close(int fd) {
1734 if (!s_ctx.initialized) {
1735 DBG("Not initialized");
1736 return -1;
1739 mfat_file_t* f = _mfat_fd_to_file(fd);
1740 if (f == NULL) {
1741 return -1;
1744 return _mfat_close_impl(f);
1747 int64_t mfat_read(int fd, void* buf, uint32_t nbyte) {
1748 if (!s_ctx.initialized) {
1749 DBG("Not initialized");
1750 return -1;
1753 mfat_file_t* f = _mfat_fd_to_file(fd);
1754 if (f == NULL || f->type != MFAT_FILE_TYPE_REGULAR) {
1755 return -1;
1758 return _mfat_read_impl(f, (uint8_t*)buf, nbyte);
1761 int64_t mfat_write(int fd, const void* buf, uint32_t nbyte) {
1762 #if MFAT_ENABLE_WRITE
1763 if (!s_ctx.initialized) {
1764 DBG("Not initialized");
1765 return -1;
1768 mfat_file_t* f = _mfat_fd_to_file(fd);
1769 if (f == NULL || f->type != MFAT_FILE_TYPE_REGULAR) {
1770 return -1;
1773 return _mfat_write_impl(f, (const uint8_t*)buf, nbyte);
1774 #else
1775 return -1;
1776 #endif
1779 int64_t mfat_lseek(int fd, int64_t offset, int whence) {
1780 if (!s_ctx.initialized) {
1781 DBG("Not initialized");
1782 return -1;
1785 mfat_file_t* f = _mfat_fd_to_file(fd);
1786 if (f == NULL || f->type != MFAT_FILE_TYPE_REGULAR) {
1787 return -1;
1790 return _mfat_lseek_impl(f, offset, whence);
1793 mfat_dir_t* mfat_fdopendir(int fd) {
1794 if (!s_ctx.initialized) {
1795 DBG("Not initialized");
1796 return NULL;
1799 return _mfat_opendir_impl(fd);
1802 mfat_dir_t* mfat_opendir(const char* path) {
1803 if (!s_ctx.initialized || s_ctx.active_partition < 0) {
1804 DBG("Not initialized");
1805 return NULL;
1808 int fd = mfat_open(path, MFAT_O_DIRECTORY | MFAT_O_RDONLY);
1809 if (fd == -1) {
1810 return NULL;
1812 return _mfat_opendir_impl(fd);
1815 int mfat_closedir(mfat_dir_t* dirp) {
1816 if (!s_ctx.initialized) {
1817 DBG("Not initialized");
1818 return -1;
1821 return _mfat_closedir_impl(dirp);
1824 mfat_dirent_t* mfat_readdir(mfat_dir_t* dirp) {
1825 if (!s_ctx.initialized) {
1826 DBG("Not initialized");
1827 return NULL;
1829 if (dirp == NULL || dirp->file == NULL) {
1830 DBG("Invalid dir");
1831 return NULL;
1834 // Advance to the next entry in the directory.
1835 return _mfat_readdir_impl(dirp);