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