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_TYPE_UNKNOWN 0
123 #define MFAT_TYPE_FAT_UNDECIDED 1 // Indicates a FAT partion of yet unknown type (FAT16 or FAT32).
124 #define MFAT_TYPE_FAT16 2
125 #define MFAT_TYPE_FAT32 3
127 // A collection of variables for keeping track of the current cluster & block position, e.g. during
128 // read/write operations.
130 uint32_t cluster_no
; ///< The cluster number.
131 uint32_t block_in_cluster
; ///< The block offset within the cluster (0..blocks_per_cluster-1).
132 uint32_t cluster_start_blk
; ///< Absolute block number of the first block of the cluster.
133 } mfat_cluster_pos_t
;
137 uint32_t first_block
;
139 uint32_t blocks_per_cluster
;
140 #if MFAT_ENABLE_WRITE
141 uint32_t num_clusters
;
143 uint32_t blocks_per_fat
;
145 uint32_t num_reserved_blocks
;
146 uint32_t root_dir_block
; // Used for FAT16.
147 uint32_t blocks_in_root_dir
; // Used for FAT16 (zero for FAT32).
148 uint32_t root_dir_cluster
; // Used for FAT32.
149 uint32_t first_data_block
;
153 // Static information about a file, as specified in the file system.
155 int part_no
; // Partition that this file is located on.
156 uint32_t size
; // Total size of file, in bytes.
157 uint32_t first_cluster
; // Starting cluster for the file.
158 uint32_t dir_entry_block
; // Block number for the directory entry of this file.
159 uint32_t dir_entry_offset
; // Offset (in bytes) into the directory entry block.
163 mfat_bool_t open
; // Is the file open?
164 int oflag
; // Flags used when opening the file.
165 uint32_t offset
; // Current byte offset relative to the file start (seek offset).
166 uint32_t current_cluster
; // Current cluster (representing the current seek offset)
167 mfat_file_info_t info
;
173 uint8_t buf
[MFAT_BLOCK_SIZE
];
174 } mfat_cached_block_t
;
177 mfat_cached_block_t block
[MFAT_NUM_CACHED_BLOCKS
];
178 #if MFAT_NUM_CACHED_BLOCKS > 1
179 // This is a priority queue: The last item in the queue is an index to the
180 // least recently used cached block item.
181 int pri
[MFAT_NUM_CACHED_BLOCKS
];
186 mfat_bool_t initialized
;
187 int active_partition
;
188 mfat_read_block_fun_t read
;
189 #if MFAT_ENABLE_WRITE
190 mfat_write_block_fun_t write
;
193 mfat_partition_t partition
[MFAT_NUM_PARTITIONS
];
194 mfat_file_t file
[MFAT_NUM_FDS
];
195 mfat_cache_t cache
[MFAT_NUM_CACHES
];
198 // Statically allocated state.
199 static mfat_ctx_t s_ctx
;
201 //--------------------------------------------------------------------------------------------------
202 // Private functions.
203 //--------------------------------------------------------------------------------------------------
205 static inline uint32_t _mfat_min(uint32_t a
, uint32_t b
) {
206 return (a
< b
) ? a
: b
;
209 static uint32_t _mfat_get_word(const uint8_t* buf
) {
210 return ((uint32_t)buf
[0]) | (((uint32_t)buf
[1]) << 8);
213 static uint32_t _mfat_get_dword(const uint8_t* buf
) {
214 return ((uint32_t)buf
[0]) | (((uint32_t)buf
[1]) << 8) | (((uint32_t)buf
[2]) << 16) |
215 (((uint32_t)buf
[3]) << 24);
218 static mfat_bool_t
_mfat_cmpbuf(const uint8_t* a
, const uint8_t* b
, const uint32_t nbyte
) {
219 for (uint32_t i
= 0; i
< nbyte
; ++i
) {
228 static mfat_bool_t
_mfat_is_fat_part_id(uint32_t id
) {
230 case MFAT_PART_ID_FAT16_LT32GB
:
231 case MFAT_PART_ID_FAT16_GT32GB
:
232 case MFAT_PART_ID_FAT16_GT32GB_LBA
:
233 case MFAT_PART_ID_FAT32
:
234 case MFAT_PART_ID_FAT32_LBA
:
240 #endif // MFAT_ENABLE_MBR
243 static mfat_bool_t
_mfat_is_fat_part_guid(const uint8_t* guid
) {
244 // Windows Basic data partition GUID = EBD0A0A2-B9E5-4433-87C0-68B6B72699C7
245 static const uint8_t BDP_GUID
[] = {0xa2,
261 if (_mfat_cmpbuf(guid
, &BDP_GUID
[0], sizeof(BDP_GUID
))) {
262 // TODO(m): These may also be NTFS partitions. How to detect?
267 #endif // MFAT_ENABLE_GPT
269 static mfat_bool_t
_mfat_is_valid_bpb(const uint8_t* bpb_buf
) {
270 // Check that the BPB signature is there.
271 if (bpb_buf
[510] != 0x55U
|| bpb_buf
[511] != 0xaaU
) {
272 DBGF("\t\tInvalid BPB signature: [%02x,%02x]", bpb_buf
[510], bpb_buf
[511]);
276 // Check for a valid jump instruction (first three bytes).
277 if (bpb_buf
[0] != 0xe9 && !(bpb_buf
[0] == 0xeb && bpb_buf
[2] == 0x90)) {
278 DBGF("\t\tInvalid BPB jump code: [%02x,%02x,%02x]", bpb_buf
[0], bpb_buf
[1], bpb_buf
[2]);
282 // Check for valid bytes per sector.
283 uint32_t bps
= _mfat_get_word(&bpb_buf
[11]);
284 if (bps
!= 512 && bps
!= 1024 && bps
!= 2048 && bps
!= 4096) {
285 DBGF("\t\tInvalid BPB bytes per sector: %" PRIu32
, bps
);
292 static mfat_cached_block_t
* _mfat_get_cached_block(uint32_t blk_no
, int cache_type
) {
293 // Pick the relevant cache.
294 mfat_cache_t
* cache
= &s_ctx
.cache
[cache_type
];
296 #if MFAT_NUM_CACHED_BLOCKS > 1
297 // By default, pick the last (least recently used) item in the pirority queue...
298 int item_id
= cache
->pri
[MFAT_NUM_CACHED_BLOCKS
- 1];
300 // ...but override it if we have a cache hit.
301 for (int i
= 0; i
< MFAT_NUM_CACHED_BLOCKS
; ++i
) {
302 mfat_cached_block_t
* cb
= &cache
->block
[i
];
303 if (cb
->state
!= MFAT_INVALID
&& cb
->blk_no
== blk_no
) {
309 // Move the selected item to the front of the priority queue (i.e. it's the most recently used
312 int prev_id
= item_id
;
313 for (int i
= 0; i
< MFAT_NUM_CACHED_BLOCKS
; ++i
) {
314 int this_id
= cache
->pri
[i
];
315 cache
->pri
[i
] = prev_id
;
316 if (this_id
== item_id
) {
323 mfat_cached_block_t
* cached_block
= &cache
->block
[item_id
];
325 mfat_cached_block_t
* cached_block
= &cache
->block
[0];
328 // Reassign the cached block to the requested block number (if necessary).
329 if (cached_block
->blk_no
!= blk_no
) {
330 #if MFAT_ENABLE_DEBUG
331 if (cached_block
->state
!= MFAT_INVALID
) {
332 DBGF("Cache %d: Evicting block %" PRIu32
" in favor of block %" PRIu32
,
334 cached_block
->blk_no
,
339 #if MFAT_ENABLE_WRITE
341 if (cached_block
->state
== MFAT_DIRTY
) {
342 DBGF("Cache %d: Flushing evicted block %" PRIu32
, cache_type
, cached_block
->blk_no
);
343 if (s_ctx
.write((const char*)cached_block
->buf
, cached_block
->blk_no
, s_ctx
.custom
) == -1) {
344 // FATAL: We can't recover from here... :-(
345 DBGF("Cache %d: Failed to flush the block", cache_type
);
351 // Set the new block ID.
352 cached_block
->blk_no
= blk_no
;
354 // The contents of the buffer is now invalid.
355 cached_block
->state
= MFAT_INVALID
;
361 static mfat_cached_block_t
* _mfat_read_block(uint32_t block_no
, int cache_type
) {
362 // First query the cache.
363 mfat_cached_block_t
* block
= _mfat_get_cached_block(block_no
, cache_type
);
368 // If necessary, read the block from storage.
369 if (block
->state
== MFAT_INVALID
) {
370 if (s_ctx
.read((char*)block
->buf
, block_no
, s_ctx
.custom
) == -1) {
373 block
->state
= MFAT_VALID
;
379 // Helper function for finding the next cluster in a cluster chain.
380 static mfat_bool_t
_mfat_next_cluster(const mfat_partition_t
* part
, uint32_t* cluster
) {
381 const uint32_t fat_entry_size
= (part
->type
== MFAT_TYPE_FAT32
) ? 4 : 2;
383 uint32_t fat_offset
= fat_entry_size
* (*cluster
);
385 part
->first_block
+ part
->num_reserved_blocks
+ (fat_offset
/ MFAT_BLOCK_SIZE
);
386 uint32_t fat_block_offset
= fat_offset
% MFAT_BLOCK_SIZE
;
388 // For FAT copy no. N (0..num_fats-1):
389 // fat_block += N * part->blocks_per_fat
391 // Read the FAT block into a cached buffer.
392 mfat_cached_block_t
* block
= _mfat_read_block(fat_block
, MFAT_CACHE_FAT
);
394 DBGF("Failed to read the FAT block %" PRIu32
, fat_block
);
397 uint8_t* buf
= &block
->buf
[0];
399 // Get the value for this cluster from the FAT.
400 uint32_t next_cluster
;
401 if (part
->type
== MFAT_TYPE_FAT32
) {
402 // For FAT32 we mask off upper 4 bits, as the cluster number is 28 bits.
403 next_cluster
= _mfat_get_dword(&buf
[fat_block_offset
]) & 0x0fffffffU
;
405 next_cluster
= _mfat_get_word(&buf
[fat_block_offset
]);
406 if (next_cluster
>= 0xfff7U
) {
407 // Convert FAT16 special codes (BAD & EOC) to FAT32 codes.
408 next_cluster
|= 0x0fff0000U
;
412 // This is a sanity check (failure indicates a corrupt filesystem). We should really do this check
413 // BEFORE accessing the cluster instead.
414 // We do not expect to see:
415 // 0x00000000 - free cluster
416 // 0x0ffffff7 - BAD cluster
417 if (next_cluster
== 0U || next_cluster
== 0x0ffffff7U
) {
418 DBGF("Unexpected next cluster: 0x%08" PRIx32
, next_cluster
);
422 DBGF("Next cluster: %" PRIu32
" (0x%08" PRIx32
")", next_cluster
, next_cluster
);
424 // Return the next cluster number.
425 *cluster
= next_cluster
;
430 // Helper function for finding the first block of a cluster.
431 static uint32_t _mfat_first_block_of_cluster(const mfat_partition_t
* part
, uint32_t cluster
) {
432 return part
->first_data_block
+ ((cluster
- 2U) * part
->blocks_per_cluster
);
435 // Initialize a cluster pos object to the current cluster & offset (e.g. for a file).
436 static mfat_cluster_pos_t
_mfat_cluster_pos_init(const mfat_partition_t
* part
,
437 const uint32_t cluster_no
,
438 const uint32_t offset
) {
439 mfat_cluster_pos_t cpos
;
440 cpos
.cluster_no
= cluster_no
;
441 cpos
.block_in_cluster
= (offset
% (part
->blocks_per_cluster
* MFAT_BLOCK_SIZE
)) / MFAT_BLOCK_SIZE
;
442 cpos
.cluster_start_blk
= _mfat_first_block_of_cluster(part
, cluster_no
);
446 // Initialize a cluster pos object to the current file offset of the specified file.
447 static mfat_cluster_pos_t
_mfat_cluster_pos_init_from_file(const mfat_partition_t
* part
,
448 const mfat_file_t
* f
) {
449 return _mfat_cluster_pos_init(part
, f
->current_cluster
, f
->offset
);
452 // Advance a cluster pos by one block.
453 static mfat_bool_t
_mfat_cluster_pos_advance(mfat_cluster_pos_t
* cpos
,
454 const mfat_partition_t
* part
) {
455 ++cpos
->block_in_cluster
;
456 if (cpos
->block_in_cluster
== part
->blocks_per_cluster
) {
457 if (!_mfat_next_cluster(part
, &cpos
->cluster_no
)) {
460 cpos
->cluster_start_blk
= _mfat_first_block_of_cluster(part
, cpos
->cluster_no
);
461 cpos
->block_in_cluster
= 0;
466 // Get the current absolute block of a cluster pos object.
467 static uint32_t _mfat_cluster_pos_blk_no(const mfat_cluster_pos_t
* cpos
) {
468 return cpos
->cluster_start_blk
+ cpos
->block_in_cluster
;
471 // Check for EOC (End of clusterchain). NOTE: EOC cluster is included in file.
472 static mfat_bool_t
_mfat_is_eoc(const uint32_t cluster
) {
473 return cluster
>= 0x0ffffff8U
;
477 static mfat_bool_t
_mfat_decode_gpt(void) {
478 // Read the primary GUID Partition Table (GPT) header, located at block 1.
479 mfat_cached_block_t
* block
= _mfat_read_block(1U, MFAT_CACHE_DATA
);
481 DBG("Failed to read the GPT");
484 uint8_t* buf
= &block
->buf
[0];
486 // Is this in fact an GPT header?
487 // TODO(m): We could do more validation (CRC etc).
488 static const uint8_t gpt_sig
[] = {0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54};
489 if (!_mfat_cmpbuf(&buf
[0], &gpt_sig
[0], sizeof(gpt_sig
))) {
490 DBG("Not a valid GPT signature");
494 // Get the start LBA of the the partition entries.
495 // Note: This is actually a 64-bit value, but we don't support such large media anyway, and by
496 // reading it out as a 32-bit little endian number we get the correct value IF the upper 32
497 // bits are zero. This should be OK as the value should always be 0x0000000000000002 for the
498 // primary copy, which we are interested in.
499 uint32_t entries_block
= _mfat_get_dword(&buf
[72]);
501 // Get the number of partition entries.
502 uint32_t num_entries
= _mfat_get_dword(&buf
[80]);
504 // Get the size of each partition entry, in bytes.
505 uint32_t entry_size
= _mfat_get_dword(&buf
[84]);
507 DBGF("GPT header: entries_block=%" PRIu32
", num_entries=%" PRIu32
", entry_size=%" PRIu32
,
512 uint32_t entry_offs
= 0;
513 for (uint32_t i
= 0; i
< num_entries
&& i
< MFAT_NUM_PARTITIONS
; ++i
) {
514 // Read the next block of the partition entry array if necessary.
515 if ((entry_offs
% MFAT_BLOCK_SIZE
) == 0) {
516 block
= _mfat_read_block(entries_block
, MFAT_CACHE_DATA
);
518 DBGF("Failed to read the GPT partition entry array at block %" PRIu32
, entries_block
);
521 buf
= &block
->buf
[0];
527 // Decode the partition entry.
528 uint8_t* entry
= &buf
[entry_offs
];
529 mfat_partition_t
* part
= &s_ctx
.partition
[i
];
531 // Get the starting block of the partition (again, 64-bit value that we read as a 32-bit
533 part
->first_block
= _mfat_get_dword(&entry
[32]);
535 // Check if the partition is bootable (bit ).
536 part
->boot
= ((entry
[48] & 0x04) != 0);
538 // Is this a potential FAT partition?
539 if (_mfat_is_fat_part_guid(&entry
[0])) {
540 // The actual FAT type is determined later.
541 part
->type
= MFAT_TYPE_FAT_UNDECIDED
;
544 // Note: We print GUIDs in byte order, as we expect them in our signatures, NOT in GUID
545 // mixed endian order.
546 DBGF("GPT entry %" PRIu32
": first_block = %" PRIu32
", GUID = %08" PRIx32
"%08" PRIx32
547 "%08" PRIx32
"%08" PRIx32
", FAT = %s",
550 _mfat_get_dword(&entry
[12]),
551 _mfat_get_dword(&entry
[8]),
552 _mfat_get_dword(&entry
[4]),
553 _mfat_get_dword(&entry
[0]),
554 part
->type
!= MFAT_TYPE_UNKNOWN
? "Yes" : "No");
556 entry_offs
+= entry_size
;
561 #endif // MFAT_ENABLE_GPT
564 static mfat_bool_t
_mfat_decode_mbr(void) {
565 mfat_cached_block_t
* block
= _mfat_read_block(0U, MFAT_CACHE_DATA
);
567 DBG("Failed to read the MBR");
570 uint8_t* buf
= &block
->buf
[0];
572 // Is this an MBR block (can also be a partition boot record)?
573 mfat_bool_t found_valid_mbr
= false;
574 if (buf
[510] == 0x55U
&& buf
[511] == 0xaaU
) {
575 // Parse each partition entry.
576 for (int i
= 0; i
< 4 && i
< MFAT_NUM_PARTITIONS
; ++i
) {
577 uint8_t* entry
= &buf
[446U + 16U * (uint32_t)i
];
578 mfat_partition_t
* part
= &s_ctx
.partition
[i
];
580 // Current state of partition (00h=Inactive, 80h=Active).
581 part
->boot
= ((entry
[0] & 0x80U
) != 0);
583 // Is this a FAT partition.
584 if (_mfat_is_fat_part_id(entry
[4])) {
585 // The actual FAT type is determined later.
586 part
->type
= MFAT_TYPE_FAT_UNDECIDED
;
587 found_valid_mbr
= true;
590 // Get the beginning of the partition (LBA).
591 part
->first_block
= _mfat_get_dword(&entry
[8]);
594 DBGF("Not a valid MBR signature: [%02x,%02x]", buf
[510], buf
[511]);
597 return found_valid_mbr
;
599 #endif // MFAT_ENABLE_MBR
601 static void _mfat_decode_tableless(void) {
602 // Some storage media are formatted without an MBR or GPT. If so, there is only a single volume
603 // and the first block is the BPB (BIOS Parameter Block) of that "partition". We initially guess
604 // that this is the case, and let the partition decoding logic assert if this was a good guess.
605 DBG("Assuming that the storage medium does not have a partition table");
607 // Clear all partitions (their values are potentially garbage).
608 for (int i
= 0; i
< MFAT_NUM_PARTITIONS
; ++i
) {
609 memset(&s_ctx
.partition
[i
], 0, sizeof(mfat_partition_t
));
612 // Guess that the first partition is FAT (we'll detect the actual type later).
613 // Note: The "first_block" field is cleared to zero in the previous memset, so the first
614 // partition starts at the first block, which is what we intend.
615 s_ctx
.partition
[0].type
= MFAT_TYPE_FAT_UNDECIDED
;
618 static mfat_bool_t
_mfat_decode_partition_tables(void) {
619 mfat_bool_t found_partition_table
= false;
622 // 1: Try to read the GUID Partition Table (GPT).
623 if (!found_partition_table
) {
624 found_partition_table
= _mfat_decode_gpt();
629 // 2: Try to read the Master Boot Record (MBR).
630 if (!found_partition_table
) {
631 found_partition_table
= _mfat_decode_mbr();
635 // 3: Assume that the storage medium does not have a partition table at all.
636 if (!found_partition_table
) {
637 _mfat_decode_tableless();
640 // Read and parse the BPB for each FAT partition.
641 for (int i
= 0; i
< MFAT_NUM_PARTITIONS
; ++i
) {
642 DBGF("Partition %d:", i
);
643 mfat_partition_t
* part
= &s_ctx
.partition
[i
];
645 // Skip unsupported partition types.
646 if (part
->type
== MFAT_TYPE_UNKNOWN
) {
647 DBG("\t\tNot a FAT partition");
651 // Load the BPB (the first block of the partition).
652 mfat_cached_block_t
* block
= _mfat_read_block(part
->first_block
, MFAT_CACHE_DATA
);
654 DBG("\t\tFailed to read the BPB");
657 uint8_t* buf
= &block
->buf
[0];
659 if (!_mfat_is_valid_bpb(buf
)) {
660 DBG("\t\tPartition does not appear to have a valid BPB");
661 part
->type
= MFAT_TYPE_UNKNOWN
;
665 // Decode useful metrics from the BPB (these are used in block number arithmetic etc).
666 uint32_t bytes_per_block
= _mfat_get_word(&buf
[11]);
667 part
->blocks_per_cluster
= buf
[13];
668 part
->num_reserved_blocks
= _mfat_get_word(&buf
[14]);
669 part
->num_fats
= buf
[16];
671 uint32_t tot_sectors_16
= _mfat_get_word(&buf
[19]);
672 uint32_t tot_sectors_32
= _mfat_get_dword(&buf
[32]);
673 part
->num_blocks
= (tot_sectors_16
!= 0) ? tot_sectors_16
: tot_sectors_32
;
676 uint32_t blocks_per_fat_16
= _mfat_get_word(&buf
[22]);
677 uint32_t blocks_per_fat_32
= _mfat_get_dword(&buf
[36]);
678 part
->blocks_per_fat
= (blocks_per_fat_16
!= 0) ? blocks_per_fat_16
: blocks_per_fat_32
;
681 uint32_t num_root_entries
= _mfat_get_word(&buf
[17]);
682 part
->blocks_in_root_dir
=
683 ((num_root_entries
* 32U) + (MFAT_BLOCK_SIZE
- 1U)) / MFAT_BLOCK_SIZE
;
686 // Derive useful metrics.
687 part
->first_data_block
= part
->first_block
+ part
->num_reserved_blocks
+
688 (part
->num_fats
* part
->blocks_per_fat
) + part
->blocks_in_root_dir
;
690 // Check that the number of bytes per sector is 512.
691 // TODO(m): We could add support for larger block sizes.
692 if (bytes_per_block
!= MFAT_BLOCK_SIZE
) {
693 DBGF("\t\tUnsupported block size: %" PRIu32
, bytes_per_block
);
694 part
->type
= MFAT_TYPE_UNKNOWN
;
698 // Up until now we have only been guessing the partition FAT type. Now we determine the *actual*
699 // partition type (FAT12, FAT16 or FAT32), using the detection algorithm given in the FAT32 File
700 // System Specification, p.14. The definition is based on the count of clusters as follows:
701 // FAT12: count of clusters < 4085
702 // FAT16: 4085 <= count of clusters < 65525
703 // FAT32: 65525 <= count of clusters
705 uint32_t root_ent_cnt
= _mfat_get_word(&buf
[17]);
706 uint32_t root_dir_sectors
= ((root_ent_cnt
* 32) + (MFAT_BLOCK_SIZE
- 1)) / MFAT_BLOCK_SIZE
;
708 uint32_t data_sectors
=
710 (part
->num_reserved_blocks
+ (part
->num_fats
* part
->blocks_per_fat
) + root_dir_sectors
);
712 uint32_t count_of_clusters
= data_sectors
/ part
->blocks_per_cluster
;
714 #if MFAT_ENABLE_WRITE
715 // We need to know the actual number of clusters when writing files.
716 part
->num_clusters
= count_of_clusters
+ 1;
719 // We don't support FAT12.
720 if (count_of_clusters
< 4085) {
721 part
->type
= MFAT_TYPE_UNKNOWN
;
722 DBG("\t\tFAT12 is not supported.");
726 if (count_of_clusters
< 65525) {
727 part
->type
= MFAT_TYPE_FAT16
;
729 part
->type
= MFAT_TYPE_FAT32
;
733 // Determine the starting block/cluster for the root directory.
734 if (part
->type
== MFAT_TYPE_FAT16
) {
735 part
->root_dir_block
= part
->first_data_block
- part
->blocks_in_root_dir
;
737 part
->root_dir_cluster
= _mfat_get_dword(&buf
[44]);
740 #if MFAT_ENABLE_DEBUG
741 // Print the partition information.
742 DBGF("\t\ttype = %s", part
->type
== MFAT_TYPE_FAT16
? "FAT16" : "FAT32");
743 DBGF("\t\tboot = %s", part
->boot
? "Yes" : "No");
744 DBGF("\t\tfirst_block = %" PRIu32
, part
->first_block
);
745 DBGF("\t\tbytes_per_block = %" PRIu32
, bytes_per_block
);
746 DBGF("\t\tnum_blocks = %" PRIu32
, part
->num_blocks
);
747 DBGF("\t\tblocks_per_cluster = %" PRIu32
, part
->blocks_per_cluster
);
748 #if MFAT_ENABLE_WRITE
749 DBGF("\t\tnum_clusters = %" PRIu32
, part
->num_clusters
);
751 DBGF("\t\tblocks_per_fat = %" PRIu32
, part
->blocks_per_fat
);
752 DBGF("\t\tnum_fats = %" PRIu32
, part
->num_fats
);
753 DBGF("\t\tnum_reserved_blocks = %" PRIu32
, part
->num_reserved_blocks
);
754 DBGF("\t\troot_dir_block = %" PRIu32
, part
->root_dir_block
);
755 DBGF("\t\troot_dir_cluster = %" PRIu32
, part
->root_dir_cluster
);
756 DBGF("\t\tblocks_in_root_dir = %" PRIu32
, part
->blocks_in_root_dir
);
757 DBGF("\t\tfirst_data_block = %" PRIu32
, part
->first_data_block
);
759 // Print the extended boot signature.
760 const uint8_t* ex_boot_sig
= (part
->type
== MFAT_TYPE_FAT32
) ? &buf
[66] : &buf
[38];
761 if (ex_boot_sig
[0] == 0x29) {
762 uint32_t vol_id
= _mfat_get_dword(&ex_boot_sig
[1]);
765 memcpy(&label
[0], &ex_boot_sig
[5], 11);
769 memcpy(&fs_type
[0], &ex_boot_sig
[16], 8);
772 DBGF("\t\tvol_id = %04" PRIx32
":%04" PRIx32
, (vol_id
>> 16), vol_id
& 0xffffU
);
773 DBGF("\t\tlabel = \"%s\"", label
);
774 DBGF("\t\tfs_type = \"%s\"", fs_type
);
776 DBGF("\t\t(Boot signature N/A - bpb[%d]=0x%02x)", (int)(ex_boot_sig
- buf
), ex_boot_sig
[0]);
778 #endif // MFAT_ENABLE_DEBUG
784 static mfat_file_t
* _mfat_get_file(int fd
) {
785 if (fd
< 0 || fd
>= MFAT_NUM_FDS
) {
786 DBGF("FD out of range: %d", fd
);
789 mfat_file_t
* f
= &s_ctx
.file
[fd
];
791 DBGF("File is not open: %d", fd
);
797 static void _mfat_dir_entry_to_stat(uint8_t* dir_entry
, mfat_stat_t
* stat
) {
798 // Decode file attributes.
799 uint32_t attr
= dir_entry
[11];
801 MFAT_S_IRUSR
| MFAT_S_IRGRP
| MFAT_S_IROTH
| MFAT_S_IXUSR
| MFAT_S_IXGRP
| MFAT_S_IXOTH
;
802 if ((attr
& MFAT_ATTR_READ_ONLY
) == 0) {
803 st_mode
|= MFAT_S_IWUSR
| MFAT_S_IWGRP
| MFAT_S_IWOTH
;
805 if ((attr
& MFAT_ATTR_DIRECTORY
) != 0) {
806 st_mode
|= MFAT_S_IFDIR
;
808 st_mode
|= MFAT_S_IFREG
;
810 stat
->st_mode
= st_mode
;
813 uint32_t time
= _mfat_get_word(&dir_entry
[22]);
814 stat
->st_mtim
.hour
= time
>> 11;
815 stat
->st_mtim
.minute
= (time
>> 5) & 63;
816 stat
->st_mtim
.second
= (time
& 31) * 2;
819 uint32_t date
= _mfat_get_word(&dir_entry
[24]);
820 stat
->st_mtim
.year
= (date
>> 9) + 1980U;
821 stat
->st_mtim
.month
= (date
>> 5) & 15;
822 stat
->st_mtim
.day
= date
& 31;
825 stat
->st_size
= _mfat_get_dword(&dir_entry
[28]);
828 static int _mfat_canonicalize_char(int c
) {
830 if ((c
>= 'A' && c
<= 'Z') || (c
>= '0' && c
<= '9') || c
== '$' || c
== '%' || c
== '-' ||
831 c
== '_' || c
== '@' || c
== '~' || c
== '`' || c
== '!' || c
== '(' || c
== ')' ||
832 c
== '{' || c
== '}' || c
== '^' || c
== '#' || c
== '&') {
836 // Convert lower case to upper case.
837 if (c
>= 'a' && c
<= 'z') {
838 return c
- 'a' + 'A';
841 // Invalid character.
845 /// @brief Canonicalize a file name.
847 /// Convert the next path part (up until a directory separator or the end of the string) into a
848 /// valid 8.3 short file name as represented in a FAT directory entry (i.e. space padded and without
849 /// the dot). File names that do not fit in an 8.3 representation will be silently truncated.
852 /// - "hello.txt" -> "HELLO TXT"
853 /// - "File.1" -> "FILE 1 "
854 /// - "ALongFileName.json" -> "ALONGFILJSO"
855 /// - "bin/foo.exe" -> "BIN " (followed by "FOO EXE" at the next invocation)
856 /// - "./foo.exe" -> "FOO EXE"
857 /// @param path A path to a file, possibly includuing directory separators (/ or \).
858 /// @param[out] fname The canonicalized file name.
859 /// @returns the index of the next path part, or -1 if this was the last path part.
860 static int _mfat_canonicalize_fname(const char* path
, char name
[12]) {
868 // Extract the name part.
870 c
= (int)(uint8_t)path
[pos
++];
871 if (c
== 0 || c
== '.' || c
== '/' || c
== '\\') {
875 name
[npos
++] = (char)_mfat_canonicalize_char(c
);
879 // Space-fill remaining characters of the name part.
880 for (; npos
< 8; ++npos
) {
884 // Extract the extension part.
887 c
= (int)(uint8_t)path
[pos
++];
888 if (c
== 0 || c
== '/' || c
== '\\') {
892 name
[npos
++] = (char)_mfat_canonicalize_char(c
);
897 // Space-fill remaining characters of the extension part.
898 for (; npos
< 11; ++npos
) {
902 // Zero terminate the string.
905 // Was this a directory part of the path (ignore trailing directory separators)?
906 if ((c
== '/' || c
== '\\') && path
[pos
] != 0) {
907 // Return the starting position of the next path part.
910 // Indicate that there are no more path parts by returning -1.
914 // Skip empty "/" and "./" directory references.
915 } while (next_idx
>= 0 &&
916 _mfat_cmpbuf((const uint8_t*)&name
[0], (const uint8_t*)" ", 11));
921 /// @brief Find a file on the given partition.
923 /// If the directory (if part of the path) exists, but the file does not exist in the directory,
924 /// this function will find the first free directory entry slot and set @c exists to false. This is
925 /// useful for creating new files.
926 /// @param part_no The partition number.
927 /// @param path The absolute path to the file.
928 /// @param[out] info Information about the file.
929 /// @param[out] is_dir true if the file is a directory, false if it is a regular file.
930 /// @param[out] exists true if the file exists, false if it needs to be created.
931 /// @returns true if the file (or its potential slot) was found.
932 static mfat_bool_t
_mfat_find_file(int part_no
,
934 mfat_file_info_t
* info
,
936 mfat_bool_t
* exists
) {
937 mfat_partition_t
* part
= &s_ctx
.partition
[part_no
];
939 // Start with the root directory cluster/block.
940 mfat_cluster_pos_t cpos
;
941 uint32_t blocks_left
;
942 if (part
->type
== MFAT_TYPE_FAT32
) {
943 cpos
= _mfat_cluster_pos_init(part
, part
->root_dir_cluster
, 0);
944 blocks_left
= 0xffffffffU
;
946 // We use a fake/tweaked cluster pos for FAT16 root directories.
947 cpos
.cluster_no
= 0U;
948 cpos
.cluster_start_blk
= part
->root_dir_block
;
949 cpos
.block_in_cluster
= 0U;
950 blocks_left
= part
->blocks_in_root_dir
;
953 // Try to find the given path.
954 mfat_cached_block_t
* block
= NULL
;
955 uint8_t* file_entry
= NULL
;
957 while (path_pos
>= 0) {
958 // Extract a directory entry compatible file name.
960 int name_pos
= _mfat_canonicalize_fname(&path
[path_pos
], fname
);
961 mfat_bool_t is_parent_dir
= (name_pos
>= 0);
962 path_pos
= is_parent_dir
? path_pos
+ name_pos
: -1;
963 DBGF("Looking for %s: \"%s\"", is_parent_dir
? "parent dir" : "file", fname
);
965 // Use an "unlimited" block counter if we're doing a clusterchain lookup.
966 if (cpos
.cluster_no
!= 0U) {
967 blocks_left
= 0xffffffffU
;
970 // Look up the file name in the directory.
971 mfat_bool_t no_more_entries
= false;
972 for (; file_entry
== NULL
&& !no_more_entries
&& blocks_left
> 0U; --blocks_left
) {
973 // Load the directory table block.
974 block
= _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos
), MFAT_CACHE_DATA
);
976 DBGF("Unable to load directory block %" PRIu32
, _mfat_cluster_pos_blk_no(&cpos
));
979 uint8_t* buf
= &block
->buf
[0];
981 // Loop over all the files in this directory block.
982 uint8_t* found_entry
= NULL
;
983 for (uint32_t offs
= 0U; offs
< 512U; offs
+= 32U) {
984 uint8_t* entry
= &buf
[offs
];
986 // TODO(m): Look for the first 0xe5-entry too in case we want to create a new file.
988 // Last entry in the directory structure?
989 if (entry
[0] == 0x00) {
990 no_more_entries
= true;
994 // Is this the file/dir that we are looking for?
995 if (_mfat_cmpbuf(&entry
[0], (const uint8_t*)&fname
[0], 11)) {
1001 // Did we have a match.
1002 if (found_entry
!= NULL
) {
1003 uint32_t attr
= found_entry
[11];
1005 // Descend into directory?
1006 if (is_parent_dir
) {
1007 if ((attr
& MFAT_ATTR_DIRECTORY
) == 0U) {
1008 DBGF("Not a directory: %s", fname
);
1012 // Decode the starting cluster of the child directory entry table.
1013 uint32_t chile_dir_cluster_no
=
1014 (_mfat_get_word(&found_entry
[20]) << 16) | _mfat_get_word(&found_entry
[26]);
1015 cpos
= _mfat_cluster_pos_init(part
, chile_dir_cluster_no
, 0);
1016 blocks_left
= 0xffffffffU
;
1018 file_entry
= found_entry
;
1024 // Go to the next block in the directory.
1025 if (cpos
.cluster_no
!= 0U) {
1026 if (!_mfat_cluster_pos_advance(&cpos
, part
)) {
1030 cpos
.block_in_cluster
+= 1; // FAT16 style linear block access.
1034 // Break loop if we didn't find the file.
1035 if (no_more_entries
) {
1040 // Could we neither find the file nor a new directory slot for the file?
1041 if (file_entry
== NULL
) {
1045 // Define the file properties.
1046 info
->part_no
= part_no
;
1047 info
->size
= _mfat_get_dword(&file_entry
[28]);
1048 info
->first_cluster
= (_mfat_get_word(&file_entry
[20]) << 16) | _mfat_get_word(&file_entry
[26]);
1049 info
->dir_entry_block
= block
->blk_no
;
1050 info
->dir_entry_offset
= file_entry
- block
->buf
;
1052 // Does the file exist?
1053 if ((file_entry
[0] != 0x00) && (file_entry
[0] != 0xe5)) {
1054 *is_dir
= (file_entry
[11] & MFAT_ATTR_DIRECTORY
) != 0U;
1064 #if MFAT_ENABLE_WRITE
1065 static void _mfat_sync_impl() {
1066 for (int j
= 0; j
< MFAT_NUM_CACHES
; ++j
) {
1067 mfat_cache_t
* cache
= &s_ctx
.cache
[j
];
1068 for (int i
= 0; i
< MFAT_NUM_CACHED_BLOCKS
; ++i
) {
1069 mfat_cached_block_t
* cb
= &cache
->block
[i
];
1070 if (cb
->state
== MFAT_DIRTY
) {
1071 DBGF("Cache: Flushing block %" PRIu32
, cb
->blk_no
);
1072 s_ctx
.write((const char*)cb
->buf
, cb
->blk_no
, s_ctx
.custom
);
1073 cb
->state
= MFAT_VALID
;
1080 static int _mfat_fstat_impl(mfat_file_info_t
* info
, mfat_stat_t
* stat
) {
1081 // Read the directory entry block (should already be in the cache).
1082 mfat_cached_block_t
* block
= _mfat_read_block(info
->dir_entry_block
, MFAT_CACHE_DATA
);
1083 if (block
== NULL
) {
1087 // Extract the relevant information for this directory entry.
1088 uint8_t* dir_entry
= &block
->buf
[info
->dir_entry_offset
];
1089 _mfat_dir_entry_to_stat(dir_entry
, stat
);
1094 static int _mfat_stat_impl(const char* path
, mfat_stat_t
* stat
) {
1095 // Find the file in the file system structure.
1098 mfat_file_info_t info
;
1099 mfat_bool_t ok
= _mfat_find_file(s_ctx
.active_partition
, path
, &info
, &is_dir
, &exists
);
1100 if (!ok
|| !exists
) {
1101 DBGF("File not found: %s", path
);
1105 return _mfat_fstat_impl(&info
, stat
);
1108 static int _mfat_open_impl(const char* path
, int oflag
) {
1109 // Find the next free fd.
1111 for (fd
= 0; fd
< MFAT_NUM_FDS
; ++fd
) {
1112 if (!s_ctx
.file
[fd
].open
) {
1116 if (fd
>= MFAT_NUM_FDS
) {
1117 DBG("No free FD:s left");
1120 mfat_file_t
* f
= &s_ctx
.file
[fd
];
1122 // Find the file in the file system structure.
1125 if (!_mfat_find_file(s_ctx
.active_partition
, path
, &f
->info
, &is_dir
, &exists
)) {
1126 DBGF("File not found: %s", path
);
1130 // Check that we found the correct type (i.e. regular file).
1132 DBGF("Can not open the directory: %s", path
);
1136 // Handle non-existing files.
1138 #if MFAT_ENABLE_WRITE
1139 // Should we create the file?
1140 if ((oflag
& MFAT_O_CREAT
) != 0U) {
1141 DBG("Creating files is not yet implemented");
1146 DBGF("File does not exist: %s", path
);
1150 // Initialize the file state.
1153 f
->current_cluster
= f
->info
.first_cluster
;
1156 DBGF("Opening file: first_cluster = %" PRIu32
" (block = %" PRIu32
"), size = %" PRIu32
1157 " bytes, dir_blk = %" PRIu32
1160 f
->info
.first_cluster
,
1161 _mfat_first_block_of_cluster(&s_ctx
.partition
[f
->info
.part_no
], f
->info
.first_cluster
),
1163 f
->info
.dir_entry_block
,
1164 f
->info
.dir_entry_offset
);
1169 static int _mfat_close_impl(mfat_file_t
* f
) {
1170 #if MFAT_ENABLE_WRITE
1171 // For good measure, we flush pending writes when a file is closed (only do this when closing
1172 // files that are open with write permissions).
1173 if ((f
->oflag
& MFAT_O_WRONLY
) != 0) {
1178 // The file is no longer open. This makes the fd available for future open() requests.
1184 static int64_t _mfat_read_impl(mfat_file_t
* f
, uint8_t* buf
, uint32_t nbyte
) {
1185 // Is the file open with read permissions?
1186 if ((f
->oflag
& MFAT_O_RDONLY
) == 0) {
1190 // Determine actual size of the operation (clamp to the size of the file).
1191 if (nbyte
> (f
->info
.size
- f
->offset
)) {
1192 nbyte
= f
->info
.size
- f
->offset
;
1193 DBGF("read: Clamped read request to %" PRIu32
" bytes", nbyte
);
1196 // Early out if only zero bytes were requested (e.g. if we are at the EOF).
1201 // Start out at the current file offset.
1202 mfat_partition_t
* part
= &s_ctx
.partition
[f
->info
.part_no
];
1203 mfat_cluster_pos_t cpos
= _mfat_cluster_pos_init_from_file(part
, f
);
1204 uint32_t bytes_read
= 0U;
1206 // Align the head of the operation to a block boundary.
1207 uint32_t block_offset
= f
->offset
% MFAT_BLOCK_SIZE
;
1208 if (block_offset
!= 0U) {
1209 // Use the block cache to get a partial block.
1210 mfat_cached_block_t
* block
= _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos
), MFAT_CACHE_DATA
);
1211 if (block
== NULL
) {
1212 DBG("Unable to read block");
1216 // Copy the data from the cache to the target buffer.
1217 uint32_t tail_bytes_in_block
= MFAT_BLOCK_SIZE
- block_offset
;
1218 uint32_t bytes_to_copy
= _mfat_min(tail_bytes_in_block
, nbyte
);
1219 memcpy(buf
, &block
->buf
[block_offset
], bytes_to_copy
);
1220 DBGF("read: Head read of %" PRIu32
" bytes", bytes_to_copy
);
1222 buf
+= bytes_to_copy
;
1223 bytes_read
+= bytes_to_copy
;
1225 // Move to the next block if we have read all the bytes of the block.
1226 if (bytes_to_copy
== tail_bytes_in_block
) {
1227 if (!_mfat_cluster_pos_advance(&cpos
, part
)) {
1233 // Read aligned blocks directly into the target buffer.
1234 while ((nbyte
- bytes_read
) >= MFAT_BLOCK_SIZE
) {
1235 if (_mfat_is_eoc(cpos
.cluster_no
)) {
1236 DBG("Unexpected cluster access after EOC");
1240 DBGF("read: Direct read of %d bytes", MFAT_BLOCK_SIZE
);
1241 if (s_ctx
.read((char*)buf
, _mfat_cluster_pos_blk_no(&cpos
), s_ctx
.custom
) == -1) {
1242 DBG("Unable to read block");
1245 buf
+= MFAT_BLOCK_SIZE
;
1246 bytes_read
+= MFAT_BLOCK_SIZE
;
1248 // Move to the next block.
1249 if (!_mfat_cluster_pos_advance(&cpos
, part
)) {
1254 // Handle the tail of the operation (unaligned tail).
1255 if (bytes_read
< nbyte
) {
1256 if (_mfat_is_eoc(cpos
.cluster_no
)) {
1257 DBG("Unexpected cluster access after EOC");
1261 // Use the block cache to get a partial block.
1262 mfat_cached_block_t
* block
= _mfat_read_block(_mfat_cluster_pos_blk_no(&cpos
), MFAT_CACHE_DATA
);
1263 if (block
== NULL
) {
1264 DBG("Unable to read block");
1268 // Copy the data from the cache to the target buffer.
1269 uint32_t bytes_to_copy
= nbyte
- bytes_read
;
1270 memcpy(buf
, &block
->buf
[0], bytes_to_copy
);
1271 DBGF("read: Tail read of %" PRIu32
" bytes", bytes_to_copy
);
1273 bytes_read
+= bytes_to_copy
;
1276 // Update file state.
1277 f
->current_cluster
= cpos
.cluster_no
;
1278 f
->offset
+= bytes_read
;
1283 #if MFAT_ENABLE_WRITE
1284 static int64_t _mfat_write_impl(mfat_file_t
* f
, const uint8_t* buf
, uint32_t nbyte
) {
1285 // Is the file open with write permissions?
1286 if ((f
->oflag
& MFAT_O_WRONLY
) == 0) {
1290 // TODO(m): Implement me!
1293 DBG("_mfat_write_impl() - not yet implemented");
1298 static int64_t _mfat_lseek_impl(mfat_file_t
* f
, int64_t offset
, int whence
) {
1299 // Calculate the new requested file offset (the arithmetic is done in 64-bit precision in order to
1300 // properly handle overflow).
1301 int64_t target_offset_64
;
1304 target_offset_64
= offset
;
1307 target_offset_64
= ((int64_t)f
->info
.size
) + offset
;
1310 target_offset_64
= ((int64_t)f
->offset
) + offset
;
1313 DBGF("Invalid whence: %d", whence
);
1317 // Sanity check the new offset.
1318 if (target_offset_64
< 0) {
1319 DBG("Seeking to a negative offset is not allowed");
1322 if (target_offset_64
> (int64_t)f
->info
.size
) {
1323 // TODO(m): POSIX lseek() allows seeking beyond the end of the file. We currently don't.
1324 DBG("Seeking beyond the end of the file is not allowed");
1328 // This type conversion is safe, since we have verified that the value fits in a 32 bit unsigned
1330 uint32_t target_offset
= (uint32_t)target_offset_64
;
1332 // Get partition info.
1333 mfat_partition_t
* part
= &s_ctx
.partition
[f
->info
.part_no
];
1334 uint32_t bytes_per_cluster
= part
->blocks_per_cluster
* MFAT_BLOCK_SIZE
;
1336 // Define the starting point for the cluster search.
1337 uint32_t current_cluster
= f
->current_cluster
;
1338 uint32_t cluster_offset
= f
->offset
- (f
->offset
% bytes_per_cluster
);
1339 if (target_offset
< cluster_offset
) {
1340 // For reverse seeking we need to start from the beginning of the file since FAT uses singly
1342 current_cluster
= f
->info
.first_cluster
;
1346 // Skip along clusters until we find the cluster that contains the requested offset.
1347 while ((target_offset
- cluster_offset
) >= bytes_per_cluster
) {
1348 if (_mfat_is_eoc(current_cluster
)) {
1349 DBG("Unexpected cluster access after EOC");
1353 // Look up the next cluster.
1354 if (!_mfat_next_cluster(part
, ¤t_cluster
)) {
1357 cluster_offset
+= bytes_per_cluster
;
1360 // Update the current offset in the file descriptor.
1361 f
->offset
= target_offset
;
1362 f
->current_cluster
= current_cluster
;
1364 return (int64_t)target_offset
;
1367 //--------------------------------------------------------------------------------------------------
1368 // Public API functions.
1369 //--------------------------------------------------------------------------------------------------
1371 int mfat_mount(mfat_read_block_fun_t read_fun
, mfat_write_block_fun_t write_fun
, void* custom
) {
1372 #if MFAT_ENABLE_WRITE
1373 if (read_fun
== NULL
|| write_fun
== NULL
) {
1375 if (read_fun
== NULL
) {
1377 DBG("Bad function pointers");
1381 // Clear the context state.
1382 memset(&s_ctx
, 0, sizeof(mfat_ctx_t
));
1383 s_ctx
.read
= read_fun
;
1384 #if MFAT_ENABLE_WRITE
1385 s_ctx
.write
= write_fun
;
1389 s_ctx
.custom
= custom
;
1390 s_ctx
.active_partition
= -1;
1392 #if MFAT_NUM_CACHED_BLOCKS > 1
1393 // Initialize the block cache priority queues.
1394 for (int j
= 0; j
< MFAT_NUM_CACHES
; ++j
) {
1395 mfat_cache_t
* cache
= &s_ctx
.cache
[j
];
1396 for (int i
= 0; i
< MFAT_NUM_CACHED_BLOCKS
; ++i
) {
1397 // Assign initial items to the priority queue.
1403 // Read the partition tables.
1404 if (!_mfat_decode_partition_tables()) {
1408 // Find the first bootable partition. If no bootable partition is found, pick the first supported
1410 int first_boot_partition
= -1;
1411 for (int i
= 0; i
< MFAT_NUM_PARTITIONS
; ++i
) {
1412 if (s_ctx
.partition
[i
].type
!= MFAT_TYPE_UNKNOWN
) {
1413 if (s_ctx
.partition
[i
].boot
&& first_boot_partition
< 0) {
1414 first_boot_partition
= i
;
1415 s_ctx
.active_partition
= i
;
1416 } else if (s_ctx
.active_partition
< 0) {
1417 s_ctx
.active_partition
= i
;
1421 if (s_ctx
.active_partition
< 0) {
1424 DBGF("Selected partition %d", s_ctx
.active_partition
);
1426 // MFAT is now initizlied.
1427 DBG("Successfully initialized");
1428 s_ctx
.initialized
= true;
1433 void mfat_unmount(void) {
1434 #if MFAT_ENABLE_WRITE
1435 // Flush any pending writes.
1438 s_ctx
.initialized
= false;
1441 int mfat_select_partition(int partition_no
) {
1442 if (!s_ctx
.initialized
) {
1443 DBG("Not initialized");
1446 if (partition_no
< 0 || partition_no
>= MFAT_NUM_PARTITIONS
) {
1447 DBGF("Bad partition number: %d", partition_no
);
1450 if (s_ctx
.partition
[partition_no
].type
== MFAT_TYPE_UNKNOWN
) {
1451 DBG("Unsupported partition type");
1455 s_ctx
.active_partition
= partition_no
;
1460 void mfat_sync(void) {
1461 #if MFAT_ENABLE_WRITE
1462 if (!s_ctx
.initialized
) {
1463 DBG("Not initialized");
1471 int mfat_fstat(int fd
, mfat_stat_t
* stat
) {
1472 if (!s_ctx
.initialized
) {
1473 DBG("Not initialized");
1477 mfat_file_t
* f
= _mfat_get_file(fd
);
1482 return _mfat_fstat_impl(&f
->info
, stat
);
1485 int mfat_stat(const char* path
, mfat_stat_t
* stat
) {
1486 if (!s_ctx
.initialized
|| s_ctx
.active_partition
< 0) {
1487 DBG("Not initialized");
1490 if (path
== NULL
|| stat
== NULL
) {
1494 return _mfat_stat_impl(path
, stat
);
1497 int mfat_open(const char* path
, int oflag
) {
1498 if (!s_ctx
.initialized
|| s_ctx
.active_partition
< 0) {
1499 DBG("Not initialized");
1502 if (path
== NULL
|| ((oflag
& MFAT_O_RDWR
) == 0)) {
1506 return _mfat_open_impl(path
, oflag
);
1509 int mfat_close(int fd
) {
1510 if (!s_ctx
.initialized
) {
1511 DBG("Not initialized");
1515 mfat_file_t
* f
= _mfat_get_file(fd
);
1520 return _mfat_close_impl(f
);
1523 int64_t mfat_read(int fd
, void* buf
, uint32_t nbyte
) {
1524 if (!s_ctx
.initialized
) {
1525 DBG("Not initialized");
1529 mfat_file_t
* f
= _mfat_get_file(fd
);
1534 return _mfat_read_impl(f
, (uint8_t*)buf
, nbyte
);
1537 int64_t mfat_write(int fd
, const void* buf
, uint32_t nbyte
) {
1538 #if MFAT_ENABLE_WRITE
1539 if (!s_ctx
.initialized
) {
1540 DBG("Not initialized");
1544 mfat_file_t
* f
= _mfat_get_file(fd
);
1549 return _mfat_write_impl(f
, (const uint8_t*)buf
, nbyte
);
1555 int64_t mfat_lseek(int fd
, int64_t offset
, int whence
) {
1556 if (!s_ctx
.initialized
) {
1557 DBG("Not initialized");
1561 mfat_file_t
* f
= _mfat_get_file(fd
);
1566 return _mfat_lseek_impl(f
, offset
, whence
);