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