1 //--------------------------------------------------------------------------------------------------
2 // Copyright (C) 2022 Marcus Geelnard
4 // Redistribution and use in source and binary forms, with or without modification, are permitted
5 // provided that the following conditions are met:
7 // 1. Redistributions of source code must retain the above copyright notice, this list of
8 // conditions and the following disclaimer.
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 //--------------------------------------------------------------------------------------------------
27 #include <string.h> // memcpy, memset
29 //--------------------------------------------------------------------------------------------------
31 //--------------------------------------------------------------------------------------------------
34 #ifndef MFAT_ENABLE_DEBUG
35 #define MFAT_ENABLE_DEBUG 0
39 #ifndef MFAT_ENABLE_WRITE
40 #define MFAT_ENABLE_WRITE 1
43 // Enable MBR support?
44 #ifndef MFAT_ENABLE_MBR
45 #define MFAT_ENABLE_MBR 1
48 // Enable GPT support?
49 #ifndef MFAT_ENABLE_GPT
50 #define MFAT_ENABLE_GPT 1
53 // Number of cached blocks.
54 #ifndef MFAT_NUM_CACHED_BLOCKS
55 #define MFAT_NUM_CACHED_BLOCKS 2
58 // Maximum number of open file descriptors.
60 #define MFAT_NUM_FDS 4
63 // Maximum number of open directories.
65 #define MFAT_NUM_DIRS 2
68 // Maximum number of partitions to support.
69 #ifndef MFAT_NUM_PARTITIONS
70 #define MFAT_NUM_PARTITIONS 4
73 //--------------------------------------------------------------------------------------------------
75 //--------------------------------------------------------------------------------------------------
79 #define DBG(_str) (void)fprintf(stderr, "[MFAT] " _str "\n")
80 #define DBGF(_fmt, ...) (void)fprintf(stderr, "[MFAT] " _fmt "\n", __VA_ARGS__)
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
;
116 // mfat_cached_block_t::state
117 #define MFAT_INVALID 0
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
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.
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
;
147 uint32_t first_block
;
149 uint32_t blocks_per_cluster
;
150 #if MFAT_ENABLE_WRITE
151 uint32_t num_clusters
;
153 uint32_t blocks_per_fat
;
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
;
163 // Static information about a file, as specified in the file system.
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.
172 // File handle, corresponding to a file descriptor (fd).
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
;
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!!!!
195 uint8_t buf
[MFAT_BLOCK_SIZE
];
196 } mfat_cached_block_t
;
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
];
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
;
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
];
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
) {
251 static mfat_bool_t
_mfat_is_fat_part_id(uint32_t 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
:
263 #endif // MFAT_ENABLE_MBR
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,
284 if (_mfat_cmpbuf(guid
, &BDP_GUID
[0], sizeof(BDP_GUID
))) {
285 // TODO(m): These may also be NTFS partitions. How to detect?
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]);
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]);
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
);
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
)) {
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
) {
341 // Move the selected item to the front of the priority queue (i.e. it's the most recently used
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
) {
355 mfat_cached_block_t
* cached_block
= &cache
->block
[item_id
];
357 mfat_cached_block_t
* cached_block
= &cache
->block
[0];
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
,
366 cached_block
->blk_no
,
371 #if MFAT_ENABLE_WRITE
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
);
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
;
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
);
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) {
405 block
->state
= MFAT_VALID
;
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
);
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
);
426 DBGF("Failed to read the FAT block %" PRIu32
, fat_block
);
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
;
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
);
454 DBGF("Next cluster: %" PRIu32
" (0x%08" PRIx32
")", next_cluster
, next_cluster
);
456 // Return the next cluster number.
457 *cluster
= next_cluster
;
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
);
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
)) {
492 cpos
->cluster_start_blk
= _mfat_first_block_of_cluster(part
, cpos
->cluster_no
);
493 cpos
->block_in_cluster
= 0;
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
;
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
);
513 DBG("Failed to read the GPT");
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");
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
,
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
);
550 DBGF("Failed to read the GPT partition entry array at block %" PRIu32
, entries_block
);
553 buf
= &block
->buf
[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
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",
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
;
593 #endif // MFAT_ENABLE_GPT
596 static mfat_bool_t
_mfat_decode_mbr(void) {
597 mfat_cached_block_t
* block
= _mfat_read_block(0U, MFAT_CACHE_DATA
);
599 DBG("Failed to read the MBR");
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]);
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;
654 // 1: Try to read the GUID Partition Table (GPT).
655 if (!found_partition_table
) {
656 found_partition_table
= _mfat_decode_gpt();
661 // 2: Try to read the Master Boot Record (MBR).
662 if (!found_partition_table
) {
663 found_partition_table
= _mfat_decode_mbr();
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");
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
);
686 DBG("\t\tFailed to read the BPB");
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
;
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
;
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
=
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;
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.");
758 if (count_of_clusters
< 65525) {
759 part
->type
= MFAT_PART_TYPE_FAT16
;
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
;
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
);
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]);
797 memcpy(&label
[0], &ex_boot_sig
[5], 11);
801 memcpy(&fs_type
[0], &ex_boot_sig
[16], 8);
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
);
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
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
);
821 mfat_file_t
* f
= &s_ctx
.file
[fd
];
823 DBGF("File is not open: %d", fd
);
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];
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
;
840 st_mode
|= MFAT_S_IFREG
;
842 stat
->st_mode
= st_mode
;
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;
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;
857 stat
->st_size
= _mfat_get_dword(&dir_entry
[28]);
860 static int _mfat_canonicalize_char(int c
) {
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
== '&') {
868 // Convert lower case to upper case.
869 if (c
>= 'a' && c
<= 'z') {
870 return c
- 'a' + 'A';
873 // Invalid character.
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.
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)
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]) {
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] == '\\')) {
901 for (int i
= 1; i
< 11; ++i
) {
905 return (path
[1] == 0) ? -1 : 2;
907 if (path
[0] == '.' && path
[1] == '.' && (path
[2] == 0 || path
[2] == '/' || path
[2] == '\\')) {
910 for (int i
= 2; i
< 11; ++i
) {
914 return (path
[2] == 0) ? -1 : 3;
917 // Extract the name part.
919 c
= (int)(uint8_t)path
[pos
++];
920 if (c
== 0 || c
== '.' || c
== '/' || c
== '\\') {
924 name
[npos
++] = (char)_mfat_canonicalize_char(c
);
928 // Space-fill remaining characters of the name part.
929 for (; npos
< 8; ++npos
) {
933 // Extract the extension part.
936 c
= (int)(uint8_t)path
[pos
++];
937 if (c
== 0 || c
== '/' || c
== '\\') {
941 name
[npos
++] = (char)_mfat_canonicalize_char(c
);
946 // Space-fill remaining characters of the extension part.
947 for (; npos
< 11; ++npos
) {
951 // Zero terminate the string.
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.
960 // Indicate that there are no more path parts by returning -1.
964 static void _mfat_make_printable_fname(const uint8_t* canonical_name
, char printable_name
[13]) {
969 while (src_idx
< 8 && canonical_name
[src_idx
] != ' ') {
970 printable_name
[dst_idx
++] = canonical_name
[src_idx
++];
975 while (src_idx
< 11 && canonical_name
[src_idx
] != ' ') {
977 printable_name
[dst_idx
++] = '.';
979 printable_name
[dst_idx
++] = canonical_name
[src_idx
++];
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
,
999 mfat_file_info_t
* info
,
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
;
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");
1025 // Try to find the given path.
1026 mfat_cached_block_t
* block
= NULL
;
1027 uint8_t* file_entry
= NULL
;
1029 // Skip leading slashes.
1030 while (*path
== '/' || *path
== '\\') {
1035 while (path_pos
>= 0) {
1036 // Extract a directory entry compatible file name.
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
));
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;
1073 // Not a file/dir entry?
1074 if (!_mfat_is_valid_shortname_file(entry
)) {
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
;
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
);
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
;
1102 file_entry
= found_entry
;
1108 // Go to the next block in the directory.
1109 if (cpos
.cluster_no
!= 0U) {
1110 if (!_mfat_cluster_pos_advance(&cpos
, part
)) {
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
) {
1125 // Could we neither find the file nor a new directory slot for the file?
1126 if (file_entry
== NULL
&& !is_root_dir
) {
1130 // Define the file properties.
1131 info
->part_no
= part_no
;
1133 // Special handling of the root directory (it does not have an entry in any directory block).
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
;
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
;
1153 // Non-existent files must be regular files, since we can't create directories with
1155 *file_type
= MFAT_FILE_TYPE_REGULAR
;
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
;
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) {
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
) {
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
);
1198 static int _mfat_stat_impl(const char* path
, mfat_stat_t
* stat
) {
1199 // Find the file in the file system structure.
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
);
1209 return _mfat_fstat_impl(&info
, stat
);
1212 static int _mfat_open_impl(const char* path
, int oflag
) {
1213 // Find the next free fd.
1215 for (fd
= 0; fd
< MFAT_NUM_FDS
; ++fd
) {
1216 if (!s_ctx
.file
[fd
].open
) {
1220 if (fd
>= MFAT_NUM_FDS
) {
1221 DBG("No free FD:s left");
1224 mfat_file_t
* f
= &s_ctx
.file
[fd
];
1226 // Find the file in the file system structure.
1229 if (!_mfat_find_file(s_ctx
.active_partition
, path
, &f
->info
, &file_type
, &exists
)) {
1230 DBGF("File not found: %s", path
);
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
);
1242 // Handle non-existing files (can only happen for regular files).
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");
1252 DBGF("File does not exist: %s", path
);
1256 // Initialize the file state.
1258 f
->type
= file_type
;
1260 f
->current_cluster
= f
->info
.first_cluster
;
1263 DBGF("Opening file: first_cluster = %" PRIu32
" (block = %" PRIu32
"), size = %" PRIu32
1264 " bytes, dir_blk = %" PRIu32
1267 f
->info
.first_cluster
,
1268 _mfat_first_block_of_cluster(&s_ctx
.partition
[f
->info
.part_no
], f
->info
.first_cluster
),
1270 f
->info
.dir_entry_block
,
1271 f
->info
.dir_entry_offset
);
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) {
1285 // The file is no longer open. This makes the fd available for future open() requests.
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) {
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).
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");
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
)) {
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");
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");
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
)) {
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");
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");
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
;
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) {
1397 // TODO(m): Implement me!
1400 DBG("_mfat_write_impl() - not yet implemented");
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
;
1411 target_offset_64
= offset
;
1414 target_offset_64
= ((int64_t)f
->info
.size
) + offset
;
1417 target_offset_64
= ((int64_t)f
->offset
) + offset
;
1420 DBGF("Invalid whence: %d", whence
);
1424 // Sanity check the new offset.
1425 if (target_offset_64
< 0) {
1426 DBG("Seeking to a negative offset is not allowed");
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");
1435 // This type conversion is safe, since we have verified that the value fits in a 32 bit unsigned
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
1449 current_cluster
= f
->info
.first_cluster
;
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");
1460 // Look up the next cluster.
1461 if (!_mfat_next_cluster(part
, ¤t_cluster
)) {
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.
1477 for (dir_id
= 0; dir_id
< MFAT_NUM_DIRS
; ++dir_id
) {
1478 if (s_ctx
.dir
[dir_id
].file
== NULL
) {
1482 if (dir_id
>= MFAT_NUM_DIRS
) {
1483 DBG("No free dir:s left");
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");
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
;
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!
1514 int _mfat_closedir_impl(mfat_dir_t
* dirp
) {
1518 if (dirp
->file
== NULL
) {
1519 DBG("The dir is already closed");
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.
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.
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.");
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
));
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
];
1565 if (entry
[0] == 0xe5) {
1569 // Last entry in the directory structure?
1570 if (entry
[0] == 0x00) {
1571 no_more_entries
= true;
1575 // A file/dir entry?
1576 if (_mfat_is_valid_shortname_file(entry
)) {
1577 // We found the next file.
1579 _mfat_make_printable_fname(entry
, dirp
->dirent
.d_name
);
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
) {
1599 if (read_fun
== NULL
) {
1601 DBG("Bad function pointers");
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
;
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.
1627 // Read the partition tables.
1628 if (!_mfat_decode_partition_tables()) {
1632 // Find the first bootable partition. If no bootable partition is found, pick the first supported
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) {
1648 DBGF("Selected partition %d", s_ctx
.active_partition
);
1650 // MFAT is now initizlied.
1651 DBG("Successfully initialized");
1652 s_ctx
.initialized
= true;
1657 void mfat_unmount(void) {
1658 #if MFAT_ENABLE_WRITE
1659 // Flush any pending writes.
1662 s_ctx
.initialized
= false;
1665 int mfat_select_partition(int partition_no
) {
1666 if (!s_ctx
.initialized
) {
1667 DBG("Not initialized");
1670 if (partition_no
< 0 || partition_no
>= MFAT_NUM_PARTITIONS
) {
1671 DBGF("Bad partition number: %d", partition_no
);
1674 if (s_ctx
.partition
[partition_no
].type
== MFAT_PART_TYPE_UNKNOWN
) {
1675 DBG("Unsupported partition type");
1679 s_ctx
.active_partition
= partition_no
;
1684 void mfat_sync(void) {
1685 #if MFAT_ENABLE_WRITE
1686 if (!s_ctx
.initialized
) {
1687 DBG("Not initialized");
1695 int mfat_fstat(int fd
, mfat_stat_t
* stat
) {
1696 if (!s_ctx
.initialized
) {
1697 DBG("Not initialized");
1701 mfat_file_t
* f
= _mfat_fd_to_file(fd
);
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");
1714 if (path
== NULL
|| stat
== NULL
) {
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");
1726 if (path
== NULL
|| ((oflag
& MFAT_O_RDWR
) == 0)) {
1730 return _mfat_open_impl(path
, oflag
);
1733 int mfat_close(int fd
) {
1734 if (!s_ctx
.initialized
) {
1735 DBG("Not initialized");
1739 mfat_file_t
* f
= _mfat_fd_to_file(fd
);
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");
1753 mfat_file_t
* f
= _mfat_fd_to_file(fd
);
1754 if (f
== NULL
|| f
->type
!= MFAT_FILE_TYPE_REGULAR
) {
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");
1768 mfat_file_t
* f
= _mfat_fd_to_file(fd
);
1769 if (f
== NULL
|| f
->type
!= MFAT_FILE_TYPE_REGULAR
) {
1773 return _mfat_write_impl(f
, (const uint8_t*)buf
, nbyte
);
1779 int64_t mfat_lseek(int fd
, int64_t offset
, int whence
) {
1780 if (!s_ctx
.initialized
) {
1781 DBG("Not initialized");
1785 mfat_file_t
* f
= _mfat_fd_to_file(fd
);
1786 if (f
== NULL
|| f
->type
!= MFAT_FILE_TYPE_REGULAR
) {
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");
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");
1808 int fd
= mfat_open(path
, MFAT_O_DIRECTORY
| MFAT_O_RDONLY
);
1812 return _mfat_opendir_impl(fd
);
1815 int mfat_closedir(mfat_dir_t
* dirp
) {
1816 if (!s_ctx
.initialized
) {
1817 DBG("Not initialized");
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");
1829 if (dirp
== NULL
|| dirp
->file
== NULL
) {
1834 // Advance to the next entry in the directory.
1835 return _mfat_readdir_impl(dirp
);