3 Reading, writing and manipulation of the directory structure on
6 Copyright (c) 2006 Michael "Chishm" Chisholm
8 Redistribution and use in source and binary forms, with or without modification,
9 are permitted provided that the following conditions are met:
11 1. Redistributions of source code must retain the above copyright notice,
12 this list of conditions and the following disclaimer.
13 2. Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation and/or
15 other materials provided with the distribution.
16 3. The name of the author may not be used to endorse or promote products derived
17 from this software without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
20 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21 AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
22 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
27 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 #include "directory.h"
39 #include "partition.h"
40 #include "file_allocation_table.h"
44 // Directory entry codes
45 #define DIR_ENTRY_LAST 0x00
46 #define DIR_ENTRY_FREE 0xE5
48 #define LAST_LFN_POS (19*13)
49 #define LAST_LFN_POS_CORRECTION (MAX_LFN_LENGTH-15)
51 typedef unsigned short ucs2_t
;
53 // Long file name directory entry
55 LFN_offset_ordinal
= 0x00, // Position within LFN
56 LFN_offset_char0
= 0x01,
57 LFN_offset_char1
= 0x03,
58 LFN_offset_char2
= 0x05,
59 LFN_offset_char3
= 0x07,
60 LFN_offset_char4
= 0x09,
61 LFN_offset_flag
= 0x0B, // Should be equal to ATTRIB_LFN
62 LFN_offset_reserved1
= 0x0C, // Always 0x00
63 LFN_offset_checkSum
= 0x0D, // Checksum of short file name (alias)
64 LFN_offset_char5
= 0x0E,
65 LFN_offset_char6
= 0x10,
66 LFN_offset_char7
= 0x12,
67 LFN_offset_char8
= 0x14,
68 LFN_offset_char9
= 0x16,
69 LFN_offset_char10
= 0x18,
70 LFN_offset_reserved2
= 0x1A, // Always 0x0000
71 LFN_offset_char11
= 0x1C,
72 LFN_offset_char12
= 0x1E
74 const int LFN_offset_table
[13]={0x01,0x03,0x05,0x07,0x09,0x0E,0x10,0x12,0x14,0x16,0x18,0x1C,0x1E};
79 const char ILLEGAL_ALIAS_CHARACTERS
[] = "\\/:;*?\"<>|&+,=[] ";
80 const char ILLEGAL_LFN_CHARACTERS
[] = "\\/:*?\"<>|";
83 Returns number of UCS-2 characters needed to encode an LFN
84 Returns -1 if it is an invalid LFN
86 #define ABOVE_UCS_RANGE 0xF0
87 static int _FAT_directory_lfnLength (const char* name
) {
91 const char* tempName
= name
;
93 nameLength
= strnlen(name
, MAX_FILENAME_LENGTH
);
94 // Make sure the name is short enough to be valid
95 if ( nameLength
>= MAX_FILENAME_LENGTH
) {
98 // Make sure it doesn't contain any invalid characters
99 if (strpbrk (name
, ILLEGAL_LFN_CHARACTERS
) != NULL
) {
102 // Make sure the name doesn't contain any control codes or codes not representable in UCS-2
103 for (i
= 0; i
< nameLength
; i
++) {
104 if (name
[i
] < 0x20 || name
[i
] >= ABOVE_UCS_RANGE
) {
108 // Convert to UCS-2 and get the resulting length
109 ucsLength
= mbsrtowcs(NULL
, &tempName
, MAX_LFN_LENGTH
, NULL
);
110 if (ucsLength
< 0 || ucsLength
>= MAX_LFN_LENGTH
) {
114 // Otherwise it is valid
119 Convert a multibyte encoded string into a NUL-terminated UCS-2 string, storing at most len characters
120 return number of characters stored
122 static size_t _FAT_directory_mbstoucs2 (ucs2_t
* dst
, const char* src
, size_t len
) {
128 while (count
< len
-1 && src
!= '\0') {
129 bytes
= mbrtowc (&tempChar
, src
, MB_CUR_MAX
, &ps
);
131 *dst
= (ucs2_t
)tempChar
;
135 } else if (bytes
== 0) {
147 Convert a UCS-2 string into a NUL-terminated multibyte string, storing at most len chars
148 return number of chars stored, or (size_t)-1 on error
150 static size_t _FAT_directory_ucs2tombs (char* dst
, const ucs2_t
* src
, size_t len
) {
154 char buff
[MB_CUR_MAX
];
157 while (count
< len
- 1 && *src
!= '\0') {
158 bytes
= wcrtomb (buff
, *src
, &ps
);
162 if (count
+ bytes
< len
&& bytes
> 0) {
163 for (i
= 0; i
< bytes
; i
++) {
178 Case-independent comparison of two multibyte encoded strings
180 static int _FAT_directory_mbsncasecmp (const char* s1
, const char* s2
, size_t len1
) {
194 b1
= mbrtowc(&wc1
, s1
, MB_CUR_MAX
, &ps1
);
195 b2
= mbrtowc(&wc2
, s2
, MB_CUR_MAX
, &ps2
);
196 if ((int)b1
< 0 || (int)b2
< 0) {
200 } while (len1
> 0 && towlower(wc1
) == towlower(wc2
) && wc1
!= 0);
202 return towlower(wc1
) - towlower(wc2
);
206 static bool _FAT_directory_entryGetAlias (const u8
* entryData
, char* destName
) {
211 if (entryData
[0] != DIR_ENTRY_FREE
) {
212 if (entryData
[0] == '.') {
214 if (entryData
[1] == '.') {
221 // Copy the filename from the dirEntry to the string
222 for (i
= 0; (i
< 8) && (entryData
[DIR_ENTRY_name
+ i
] != ' '); i
++) {
223 destName
[i
] = entryData
[DIR_ENTRY_name
+ i
];
225 // Copy the extension from the dirEntry to the string
226 if (entryData
[DIR_ENTRY_extension
] != ' ') {
228 for ( j
= 0; (j
< 3) && (entryData
[DIR_ENTRY_extension
+ j
] != ' '); j
++) {
229 destName
[i
++] = entryData
[DIR_ENTRY_extension
+ j
];
236 return (destName
[0] != '\0');
239 uint32_t _FAT_directory_entryGetCluster (PARTITION
* partition
, const uint8_t* entryData
) {
240 if (partition
->filesysType
== FS_FAT32
) {
241 // Only use high 16 bits of start cluster when we are certain they are correctly defined
242 return u8array_to_u16(entryData
,DIR_ENTRY_cluster
) | (u8array_to_u16(entryData
, DIR_ENTRY_clusterHigh
) << 16);
244 return u8array_to_u16(entryData
,DIR_ENTRY_cluster
);
248 static bool _FAT_directory_incrementDirEntryPosition (PARTITION
* partition
, DIR_ENTRY_POSITION
* entryPosition
, bool extendDirectory
) {
249 DIR_ENTRY_POSITION position
= *entryPosition
;
250 uint32_t tempCluster
;
252 // Increment offset, wrapping at the end of a sector
254 if (position
.offset
== BYTES_PER_READ
/ DIR_ENTRY_DATA_SIZE
) {
256 // Increment sector when wrapping
258 // But wrap at the end of a cluster
259 if ((position
.sector
== partition
->sectorsPerCluster
) && (position
.cluster
!= FAT16_ROOT_DIR_CLUSTER
)) {
261 // Move onto the next cluster, making sure there is another cluster to go to
262 tempCluster
= _FAT_fat_nextCluster(partition
, position
.cluster
);
263 if (tempCluster
== CLUSTER_EOF
) {
264 if (extendDirectory
) {
265 tempCluster
= _FAT_fat_linkFreeClusterCleared (partition
, position
.cluster
);
266 if (!_FAT_fat_isValidCluster(partition
, tempCluster
)) {
267 return false; // This will only happen if the disc is full
270 return false; // Got to the end of the directory, not extending it
273 position
.cluster
= tempCluster
;
274 } else if ((position
.cluster
== FAT16_ROOT_DIR_CLUSTER
) && (position
.sector
== (partition
->dataStart
- partition
->rootDirStart
))) {
275 return false; // Got to end of root directory, can't extend it
278 *entryPosition
= position
;
282 bool _FAT_directory_getNextEntry (PARTITION
* partition
, DIR_ENTRY
* entry
) {
283 DIR_ENTRY_POSITION entryStart
;
284 DIR_ENTRY_POSITION entryEnd
;
285 uint8_t entryData
[0x20];
286 ucs2_t lfn
[MAX_LFN_LENGTH
];
287 bool notFound
, found
;
289 uint8_t lfnChkSum
, chkSum
;
295 entryStart
= entry
->dataEnd
;
297 // Make sure we are using the correct root directory, in case of FAT32
298 if (entryStart
.cluster
== FAT16_ROOT_DIR_CLUSTER
) {
299 entryStart
.cluster
= partition
->rootDirCluster
;
302 entryEnd
= entryStart
;
309 while (!found
&& !notFound
) {
310 if (_FAT_directory_incrementDirEntryPosition (partition
, &entryEnd
, false) == false) {
314 _FAT_cache_readPartialSector (partition
->cache
, entryData
,
315 _FAT_fat_clusterToSector(partition
, entryEnd
.cluster
) + entryEnd
.sector
,
316 entryEnd
.offset
* DIR_ENTRY_DATA_SIZE
, DIR_ENTRY_DATA_SIZE
);
318 if (entryData
[DIR_ENTRY_attributes
] == ATTRIB_LFN
) {
320 if (entryData
[LFN_offset_ordinal
] & LFN_DEL
) {
322 } else if (entryData
[LFN_offset_ordinal
] & LFN_END
) {
323 // Last part of LFN, make sure it isn't deleted using previous if(Thanks MoonLight)
324 entryStart
= entryEnd
; // This is the start of a directory entry
326 lfnPos
= (entryData
[LFN_offset_ordinal
] & ~LFN_END
) * 13;
327 if (lfnPos
> MAX_LFN_LENGTH
- 1) {
328 lfnPos
= MAX_LFN_LENGTH
- 1;
330 lfn
[lfnPos
] = '\0'; // Set end of lfn to null character
331 lfnChkSum
= entryData
[LFN_offset_checkSum
];
332 } if (lfnChkSum
!= entryData
[LFN_offset_checkSum
]) {
336 lfnPos
= ((entryData
[LFN_offset_ordinal
] & ~LFN_END
) - 1) * 13;
337 if (lfnPos
> LAST_LFN_POS
) {
338 // Force it within the buffer. Will corrupt the filename but prevent buffer overflows
339 lfnPos
= LAST_LFN_POS
;
341 for (i
= 0; i
< 13; i
++) {
342 lfn
[lfnPos
+ i
] = entryData
[LFN_offset_table
[i
]] | (entryData
[LFN_offset_table
[i
]+1] << 8);
345 } else if (entryData
[DIR_ENTRY_attributes
] & ATTRIB_VOL
) {
346 // This is a volume name, don't bother with it
347 } else if (entryData
[0] == DIR_ENTRY_LAST
) {
349 } else if ((entryData
[0] != DIR_ENTRY_FREE
) && (entryData
[0] > 0x20) && !(entryData
[DIR_ENTRY_attributes
] & ATTRIB_VOL
)) {
351 // Calculate file checksum
353 for (i
=0; i
< 11; i
++) {
354 // NOTE: The operation is an unsigned char rotate right
355 chkSum
= ((chkSum
& 1) ? 0x80 : 0) + (chkSum
>> 1) + entryData
[i
];
357 if (chkSum
!= lfnChkSum
) {
359 entry
->filename
[0] = '\0';
364 if (_FAT_directory_ucs2tombs (entry
->filename
, lfn
, MAX_FILENAME_LENGTH
) == (size_t)-1) {
365 // Failed to convert the file name to UTF-8. Maybe the wrong locale is set?
369 entryStart
= entryEnd
;
370 _FAT_directory_entryGetAlias (entryData
, entry
->filename
);
376 // If no file is found, return false
380 // Fill in the directory entry struct
381 entry
->dataStart
= entryStart
;
382 entry
->dataEnd
= entryEnd
;
383 memcpy (entry
->entryData
, entryData
, DIR_ENTRY_DATA_SIZE
);
388 bool _FAT_directory_getFirstEntry (PARTITION
* partition
, DIR_ENTRY
* entry
, uint32_t dirCluster
) {
389 entry
->dataStart
.cluster
= dirCluster
;
390 entry
->dataStart
.sector
= 0;
391 entry
->dataStart
.offset
= -1; // Start before the beginning of the directory
393 entry
->dataEnd
= entry
->dataStart
;
395 return _FAT_directory_getNextEntry (partition
, entry
);
398 bool _FAT_directory_getRootEntry (PARTITION
* partition
, DIR_ENTRY
* entry
) {
399 entry
->dataStart
.cluster
= 0;
400 entry
->dataStart
.sector
= 0;
401 entry
->dataStart
.offset
= 0;
403 entry
->dataEnd
= entry
->dataStart
;
405 memset (entry
->filename
, '\0', MAX_FILENAME_LENGTH
);
406 entry
->filename
[0] = '.';
408 memset (entry
->entryData
, 0, DIR_ENTRY_DATA_SIZE
);
409 memset (entry
->entryData
, ' ', 11);
410 entry
->entryData
[0] = '.';
412 entry
->entryData
[DIR_ENTRY_attributes
] = ATTRIB_DIR
;
414 u16_to_u8array (entry
->entryData
, DIR_ENTRY_cluster
, partition
->rootDirCluster
);
415 u16_to_u8array (entry
->entryData
, DIR_ENTRY_clusterHigh
, partition
->rootDirCluster
>> 16);
420 bool _FAT_directory_entryFromPosition (PARTITION
* partition
, DIR_ENTRY
* entry
) {
421 DIR_ENTRY_POSITION entryStart
= entry
->dataStart
;
422 DIR_ENTRY_POSITION entryEnd
= entry
->dataEnd
;
423 bool entryStillValid
;
425 ucs2_t lfn
[MAX_LFN_LENGTH
];
428 uint8_t entryData
[DIR_ENTRY_DATA_SIZE
];
430 memset (entry
->filename
, '\0', MAX_FILENAME_LENGTH
);
432 // Create an empty directory entry to overwrite the old ones with
433 for ( entryStillValid
= true, finished
= false;
434 entryStillValid
&& !finished
;
435 entryStillValid
= _FAT_directory_incrementDirEntryPosition (partition
, &entryStart
, false))
437 _FAT_cache_readPartialSector (partition
->cache
, entryData
,
438 _FAT_fat_clusterToSector(partition
, entryStart
.cluster
) + entryStart
.sector
,
439 entryStart
.offset
* DIR_ENTRY_DATA_SIZE
, DIR_ENTRY_DATA_SIZE
);
441 if ((entryStart
.cluster
== entryEnd
.cluster
)
442 && (entryStart
.sector
== entryEnd
.sector
)
443 && (entryStart
.offset
== entryEnd
.offset
)) {
444 // Copy the entry data and stop, since this is the last section of the directory entry
445 memcpy (entry
->entryData
, entryData
, DIR_ENTRY_DATA_SIZE
);
448 // Copy the long file name data
449 lfnPos
= ((entryData
[LFN_offset_ordinal
] & ~LFN_END
) - 1) * 13;
450 if (lfnPos
> LAST_LFN_POS
) {
451 lfnPos
= LAST_LFN_POS_CORRECTION
;
453 for (i
= 0; i
< 13; i
++) {
454 lfn
[lfnPos
+ i
] = entryData
[LFN_offset_table
[i
]] | (entryData
[LFN_offset_table
[i
]+1] << 8);
459 if (!entryStillValid
) {
463 if ((entryStart
.cluster
== entryEnd
.cluster
)
464 && (entryStart
.sector
== entryEnd
.sector
)
465 && (entryStart
.offset
== entryEnd
.offset
)) {
466 // Since the entry doesn't have a long file name, extract the short filename
467 if (!_FAT_directory_entryGetAlias (entry
->entryData
, entry
->filename
)) {
471 // Encode the long file name into a multibyte string
472 if (_FAT_directory_ucs2tombs (entry
->filename
, lfn
, MAX_FILENAME_LENGTH
) == (size_t)-1) {
482 bool _FAT_directory_entryFromPath (PARTITION
* partition
, DIR_ENTRY
* entry
, const char* path
, const char* pathEnd
) {
483 size_t dirnameLength
;
484 const char* pathPosition
;
485 const char* nextPathPosition
;
488 char alias
[MAX_ALIAS_LENGTH
];
489 bool found
, notFound
;
496 if (pathEnd
== NULL
) {
497 // Set pathEnd to the end of the path string
498 pathEnd
= strchr (path
, '\0');
501 if (pathPosition
[0] == DIR_SEPARATOR
) {
502 // Start at root directory
503 dirCluster
= partition
->rootDirCluster
;
504 // Consume separator(s)
505 while (pathPosition
[0] == DIR_SEPARATOR
) {
508 // If the path is only specifying a directory in the form of "/" return it
509 if (pathPosition
>= pathEnd
) {
510 _FAT_directory_getRootEntry (partition
, entry
);
514 // Start in current working directory
515 dirCluster
= partition
->cwdCluster
;
518 // If the path is only specifying a directory in the form "."
519 // and this is the root directory, return it
520 if ((dirCluster
== partition
->rootDirCluster
) && (strcmp(".", pathPosition
) == 0)) {
521 _FAT_directory_getRootEntry (partition
, entry
);
525 while (!found
&& !notFound
) {
526 // Get the name of the next required subdirectory within the path
527 nextPathPosition
= strchr (pathPosition
, DIR_SEPARATOR
);
528 if (nextPathPosition
!= NULL
) {
529 dirnameLength
= nextPathPosition
- pathPosition
;
531 dirnameLength
= strlen(pathPosition
);
534 if (dirnameLength
> MAX_FILENAME_LENGTH
) {
535 // The path is too long to bother with
539 // Look for the directory within the path
540 foundFile
= _FAT_directory_getFirstEntry (partition
, entry
, dirCluster
);
542 while (foundFile
&& !found
&& !notFound
) { // It hasn't already found the file
543 // Check if the filename matches
544 if ((dirnameLength
== strnlen(entry
->filename
, MAX_FILENAME_LENGTH
))
545 && (_FAT_directory_mbsncasecmp(pathPosition
, entry
->filename
, dirnameLength
) == 0)) {
549 // Check if the alias matches
550 _FAT_directory_entryGetAlias (entry
->entryData
, alias
);
551 if ((dirnameLength
== strnlen(alias
, MAX_ALIAS_LENGTH
))
552 && (_FAT_directory_mbsncasecmp(pathPosition
, alias
, dirnameLength
) == 0)) {
556 if (found
&& !(entry
->entryData
[DIR_ENTRY_attributes
] & ATTRIB_DIR
) && (nextPathPosition
!= NULL
)) {
557 // Make sure that we aren't trying to follow a file instead of a directory in the path
562 foundFile
= _FAT_directory_getNextEntry (partition
, entry
);
567 // Check that the search didn't get to the end of the directory
570 } else if ((nextPathPosition
== NULL
) || (nextPathPosition
>= pathEnd
)) {
571 // Check that we reached the end of the path
573 } else if (entry
->entryData
[DIR_ENTRY_attributes
] & ATTRIB_DIR
) {
574 dirCluster
= _FAT_directory_entryGetCluster (partition
, entry
->entryData
);
575 pathPosition
= nextPathPosition
;
576 // Consume separator(s)
577 while (pathPosition
[0] == DIR_SEPARATOR
) {
580 // The requested directory was found
581 if (pathPosition
>= pathEnd
) {
589 if (found
&& !notFound
) {
590 if (partition
->filesysType
== FS_FAT32
&& (entry
->entryData
[DIR_ENTRY_attributes
] & ATTRIB_DIR
) &&
591 _FAT_directory_entryGetCluster (partition
, entry
->entryData
) == CLUSTER_ROOT
)
593 // On FAT32 it should specify an actual cluster for the root entry,
594 // not cluster 0 as on FAT16
595 _FAT_directory_getRootEntry (partition
, entry
);
603 bool _FAT_directory_removeEntry (PARTITION
* partition
, DIR_ENTRY
* entry
) {
604 DIR_ENTRY_POSITION entryStart
= entry
->dataStart
;
605 DIR_ENTRY_POSITION entryEnd
= entry
->dataEnd
;
606 bool entryStillValid
;
608 uint8_t entryData
[DIR_ENTRY_DATA_SIZE
];
610 // Create an empty directory entry to overwrite the old ones with
611 for ( entryStillValid
= true, finished
= false;
612 entryStillValid
&& !finished
;
613 entryStillValid
= _FAT_directory_incrementDirEntryPosition (partition
, &entryStart
, false))
615 _FAT_cache_readPartialSector (partition
->cache
, entryData
, _FAT_fat_clusterToSector(partition
, entryStart
.cluster
) + entryStart
.sector
, entryStart
.offset
* DIR_ENTRY_DATA_SIZE
, DIR_ENTRY_DATA_SIZE
);
616 entryData
[0] = DIR_ENTRY_FREE
;
617 _FAT_cache_writePartialSector (partition
->cache
, entryData
, _FAT_fat_clusterToSector(partition
, entryStart
.cluster
) + entryStart
.sector
, entryStart
.offset
* DIR_ENTRY_DATA_SIZE
, DIR_ENTRY_DATA_SIZE
);
618 if ((entryStart
.cluster
== entryEnd
.cluster
) && (entryStart
.sector
== entryEnd
.sector
) && (entryStart
.offset
== entryEnd
.offset
)) {
623 if (!entryStillValid
) {
630 static bool _FAT_directory_findEntryGap (PARTITION
* partition
, DIR_ENTRY
* entry
, uint32_t dirCluster
, size_t size
) {
631 DIR_ENTRY_POSITION gapStart
;
632 DIR_ENTRY_POSITION gapEnd
;
633 uint8_t entryData
[DIR_ENTRY_DATA_SIZE
];
634 size_t dirEntryRemain
;
635 bool endOfDirectory
, entryStillValid
;
637 // Scan Dir for free entry
640 gapEnd
.cluster
= dirCluster
;
644 entryStillValid
= true;
645 dirEntryRemain
= size
;
646 endOfDirectory
= false;
648 while (entryStillValid
&& !endOfDirectory
&& (dirEntryRemain
> 0)) {
649 _FAT_cache_readPartialSector (partition
->cache
, entryData
,
650 _FAT_fat_clusterToSector(partition
, gapEnd
.cluster
) + gapEnd
.sector
,
651 gapEnd
.offset
* DIR_ENTRY_DATA_SIZE
, DIR_ENTRY_DATA_SIZE
);
652 if (entryData
[0] == DIR_ENTRY_LAST
) {
655 endOfDirectory
= true;
656 } else if (entryData
[0] == DIR_ENTRY_FREE
) {
657 if (dirEntryRemain
== size
) {
662 dirEntryRemain
= size
;
665 if (!endOfDirectory
&& (dirEntryRemain
> 0)) {
666 entryStillValid
= _FAT_directory_incrementDirEntryPosition (partition
, &gapEnd
, true);
670 // Make sure the scanning didn't fail
671 if (!entryStillValid
) {
675 // Save the start entry, since we know it is valid
676 entry
->dataStart
= gapStart
;
678 if (endOfDirectory
) {
679 memset (entryData
, DIR_ENTRY_LAST
, DIR_ENTRY_DATA_SIZE
);
680 dirEntryRemain
+= 1; // Increase by one to take account of End Of Directory Marker
681 while ((dirEntryRemain
> 0) && entryStillValid
) {
682 // Get the gapEnd before incrementing it, so the second to last one is saved
683 entry
->dataEnd
= gapEnd
;
684 // Increment gapEnd, moving onto the next entry
685 entryStillValid
= _FAT_directory_incrementDirEntryPosition (partition
, &gapEnd
, true);
687 // Fill the entry with blanks
688 _FAT_cache_writePartialSector (partition
->cache
, entryData
,
689 _FAT_fat_clusterToSector(partition
, gapEnd
.cluster
) + gapEnd
.sector
,
690 gapEnd
.offset
* DIR_ENTRY_DATA_SIZE
, DIR_ENTRY_DATA_SIZE
);
692 if (!entryStillValid
) {
696 entry
->dataEnd
= gapEnd
;
702 static bool _FAT_directory_entryExists (PARTITION
* partition
, const char* name
, uint32_t dirCluster
) {
705 char alias
[MAX_ALIAS_LENGTH
];
706 size_t dirnameLength
;
708 dirnameLength
= strnlen(name
, MAX_FILENAME_LENGTH
);
710 if (dirnameLength
>= MAX_FILENAME_LENGTH
) {
714 // Make sure the entry doesn't already exist
715 foundFile
= _FAT_directory_getFirstEntry (partition
, &tempEntry
, dirCluster
);
717 while (foundFile
) { // It hasn't already found the file
718 // Check if the filename matches
719 if ((dirnameLength
== strnlen(tempEntry
.filename
, MAX_FILENAME_LENGTH
))
720 && (_FAT_directory_mbsncasecmp(name
, tempEntry
.filename
, dirnameLength
) == 0)) {
724 // Check if the alias matches
725 _FAT_directory_entryGetAlias (tempEntry
.entryData
, alias
);
726 if ((dirnameLength
== strnlen(alias
, MAX_ALIAS_LENGTH
))
727 && (_FAT_directory_mbsncasecmp(name
, alias
, dirnameLength
) == 0)) {
730 foundFile
= _FAT_directory_getNextEntry (partition
, &tempEntry
);
736 Creates an alias for a long file name. If the alias is not an exact match for the
737 filename, it returns the number of characters in the alias. If the two names match,
738 it returns 0. If there was an error, it returns -1.
740 static int _FAT_directory_createAlias (char* alias
, const char* lfn
) {
741 bool lossyConversion
= false; // Set when the alias had to be modified to be valid
751 // Strip leading periods
752 while (lfn
[lfnPos
] == '.') {
754 lossyConversion
= true;
757 // Primary portion of alias
758 while (aliasPos
< 8 && lfn
[lfnPos
] != '.' && lfn
[lfnPos
] != '\0') {
759 bytesUsed
= mbrtowc(&lfnChar
, lfn
+ lfnPos
, MAX_FILENAME_LENGTH
- lfnPos
, &ps
);
763 oemChar
= wctob(towupper((wint_t)lfnChar
));
764 if (wctob((wint_t)lfnChar
) != oemChar
) {
765 // Case of letter was changed
766 lossyConversion
= true;
768 if (oemChar
== ' ') {
769 // Skip spaces in filename
770 lossyConversion
= true;
774 if (oemChar
== EOF
) {
775 oemChar
= '_'; // Replace unconvertable characters with underscores
776 lossyConversion
= true;
778 if (strchr (ILLEGAL_ALIAS_CHARACTERS
, oemChar
) != NULL
) {
779 // Invalid Alias character
780 oemChar
= '_'; // Replace illegal characters with underscores
781 lossyConversion
= true;
784 alias
[aliasPos
] = (char)oemChar
;
789 if (lfn
[lfnPos
] != '.' && lfn
[lfnPos
] != '\0') {
790 // Name was more than 8 characters long
791 lossyConversion
= true;
795 lfnExt
= strrchr (lfn
, '.');
796 if (lfnExt
!= NULL
&& lfnExt
!= strchr (lfn
, '.')) {
797 // More than one period in name
798 lossyConversion
= true;
800 if (lfnExt
!= NULL
&& lfnExt
[1] != '\0') {
802 alias
[aliasPos
] = '.';
804 memset (&ps
, 0, sizeof(ps
));
805 for (aliasExtLen
= 0; aliasExtLen
< MAX_ALIAS_EXT_LENGTH
&& *lfnExt
!= '\0'; aliasExtLen
++) {
806 bytesUsed
= mbrtowc(&lfnChar
, lfnExt
, MAX_FILENAME_LENGTH
- lfnPos
, &ps
);
810 oemChar
= wctob(towupper((wint_t)lfnChar
));
811 if (wctob((wint_t)lfnChar
) != oemChar
) {
812 // Case of letter was changed
813 lossyConversion
= true;
815 if (oemChar
== ' ') {
816 // Skip spaces in alias
817 lossyConversion
= true;
821 if (oemChar
== EOF
) {
822 oemChar
= '_'; // Replace unconvertable characters with underscores
823 lossyConversion
= true;
825 if (strchr (ILLEGAL_ALIAS_CHARACTERS
, oemChar
) != NULL
) {
826 // Invalid Alias character
827 oemChar
= '_'; // Replace illegal characters with underscores
828 lossyConversion
= true;
831 alias
[aliasPos
] = (char)oemChar
;
835 if (*lfnExt
!= '\0') {
836 // Extension was more than 3 characters long
837 lossyConversion
= true;
841 alias
[aliasPos
] = '\0';
842 if (lossyConversion
) {
849 bool _FAT_directory_addEntry (PARTITION
* partition
, DIR_ENTRY
* entry
, uint32_t dirCluster
) {
851 uint8_t lfnEntry
[DIR_ENTRY_DATA_SIZE
];
852 int i
,j
; // Must be signed for use when decrementing in for loop
854 DIR_ENTRY_POSITION curEntryPos
;
855 bool entryStillValid
;
856 uint8_t aliasCheckSum
= 0;
857 char alias
[MAX_ALIAS_LENGTH
];
861 // Make sure the filename is not 0 length
862 if (strnlen (entry
->filename
, MAX_FILENAME_LENGTH
) < 1) {
866 // Make sure the filename is at least a valid LFN
867 lfnLen
= _FAT_directory_lfnLength (entry
->filename
);
872 // Remove trailing spaces
873 for (i
= strlen (entry
->filename
) - 1; (i
> 0) && (entry
->filename
[i
] == ' '); --i
) {
874 entry
->filename
[i
] = '\0';
876 // Remove leading spaces
877 for (i
= 0; (i
< (int)strlen (entry
->filename
)) && (entry
->filename
[i
] == ' '); ++i
) ;
879 memmove (entry
->filename
, entry
->filename
+ i
, strlen (entry
->filename
+ i
));
882 // Remove junk in filename
883 i
= strlen (entry
->filename
);
884 memset (entry
->filename
+ i
, '\0', MAX_FILENAME_LENGTH
- i
);
886 // Make sure the entry doesn't already exist
887 if (_FAT_directory_entryExists (partition
, entry
->filename
, dirCluster
)) {
891 // Clear out alias, so we can generate a new one
892 memset (entry
->entryData
, ' ', 11);
894 if ( strncmp(entry
->filename
, ".", MAX_FILENAME_LENGTH
) == 0) {
896 entry
->entryData
[0] = '.';
898 } else if ( strncmp(entry
->filename
, "..", MAX_FILENAME_LENGTH
) == 0) {
900 entry
->entryData
[0] = '.';
901 entry
->entryData
[1] = '.';
905 aliasLen
= _FAT_directory_createAlias (alias
, entry
->filename
);
908 } else if (aliasLen
== 0) {
909 // It's a normal short filename
912 // It's a long filename with an alias
913 entrySize
= ((lfnLen
+ LFN_ENTRY_LENGTH
- 1) / LFN_ENTRY_LENGTH
) + 1;
915 // Generate full alias for all cases except when the alias is simply an upper case version of the LFN
916 // and there isn't already a file with that name
917 if (strncasecmp (alias
, entry
->filename
, MAX_ALIAS_LENGTH
) != 0 ||
918 _FAT_directory_entryExists (partition
, alias
, dirCluster
))
920 // expand primary part to 8 characters long by padding the end with underscores
921 i
= MAX_ALIAS_PRI_LENGTH
- 1;
922 // Move extension to last 3 characters
923 while (alias
[i
] != '.' && i
> 0) i
--;
925 j
= MAX_ALIAS_LENGTH
- MAX_ALIAS_EXT_LENGTH
- 2; // 1 char for '.', one for NUL, 3 for extension
926 memmove (alias
+ j
, alias
+ i
, strlen(alias
) - i
);
927 // Pad primary component
928 memset (alias
+ i
, '_', j
- i
);
931 // Generate numeric tail
932 for (i
= 1; i
<= MAX_NUMERIC_TAIL
; i
++) {
934 tmpCharPtr
= alias
+ MAX_ALIAS_PRI_LENGTH
- 1;
936 *tmpCharPtr
= '0' + (j
% 10); // ASCII numeric value
941 if (!_FAT_directory_entryExists (partition
, alias
, dirCluster
)) {
945 if (i
> MAX_NUMERIC_TAIL
) {
946 // Couldn't get a valid alias
952 // Copy alias or short file name into directory entry data
953 for (i
= 0, j
= 0; (j
< 8) && (alias
[i
] != '.') && (alias
[i
] != '\0'); i
++, j
++) {
954 entry
->entryData
[j
] = alias
[i
];
957 entry
->entryData
[j
] = ' ';
960 if (alias
[i
] == '.') {
963 while ((alias
[i
] != '\0') && (j
< 11)) {
964 entry
->entryData
[j
] = alias
[i
];
970 entry
->entryData
[j
] = ' ';
974 // Generate alias checksum
975 for (i
=0; i
< ALIAS_ENTRY_LENGTH
; i
++) {
976 // NOTE: The operation is an unsigned char rotate right
977 aliasCheckSum
= ((aliasCheckSum
& 1) ? 0x80 : 0) + (aliasCheckSum
>> 1) + entry
->entryData
[i
];
981 // Find or create space for the entry
982 if (_FAT_directory_findEntryGap (partition
, entry
, dirCluster
, entrySize
) == false) {
986 // Write out directory entry
987 curEntryPos
= entry
->dataStart
;
990 // lfn is only pushed onto the stack here, reducing overall stack usage
991 ucs2_t lfn
[MAX_LFN_LENGTH
] = {0};
992 _FAT_directory_mbstoucs2 (lfn
, entry
->filename
, MAX_LFN_LENGTH
);
994 for (entryStillValid
= true, i
= entrySize
; entryStillValid
&& i
> 0;
995 entryStillValid
= _FAT_directory_incrementDirEntryPosition (partition
, &curEntryPos
, false), -- i
)
998 // Long filename entry
999 lfnEntry
[LFN_offset_ordinal
] = (i
- 1) | ((size_t)i
== entrySize
? LFN_END
: 0);
1000 for (j
= 0; j
< 13; j
++) {
1001 if (lfn
[(i
- 2) * 13 + j
] == '\0') {
1002 if ((j
> 1) && (lfn
[(i
- 2) * 13 + (j
-1)] == '\0')) {
1003 u16_to_u8array (lfnEntry
, LFN_offset_table
[j
], 0xffff); // Padding
1005 u16_to_u8array (lfnEntry
, LFN_offset_table
[j
], 0x0000); // Terminating null character
1008 u16_to_u8array (lfnEntry
, LFN_offset_table
[j
], lfn
[(i
- 2) * 13 + j
]);
1012 lfnEntry
[LFN_offset_checkSum
] = aliasCheckSum
;
1013 lfnEntry
[LFN_offset_flag
] = ATTRIB_LFN
;
1014 lfnEntry
[LFN_offset_reserved1
] = 0;
1015 u16_to_u8array (lfnEntry
, LFN_offset_reserved2
, 0);
1016 _FAT_cache_writePartialSector (partition
->cache
, lfnEntry
, _FAT_fat_clusterToSector(partition
, curEntryPos
.cluster
) + curEntryPos
.sector
, curEntryPos
.offset
* DIR_ENTRY_DATA_SIZE
, DIR_ENTRY_DATA_SIZE
);
1018 // Alias & file data
1019 _FAT_cache_writePartialSector (partition
->cache
, entry
->entryData
, _FAT_fat_clusterToSector(partition
, curEntryPos
.cluster
) + curEntryPos
.sector
, curEntryPos
.offset
* DIR_ENTRY_DATA_SIZE
, DIR_ENTRY_DATA_SIZE
);
1027 bool _FAT_directory_chdir (PARTITION
* partition
, const char* path
) {
1030 if (!_FAT_directory_entryFromPath (partition
, &entry
, path
, NULL
)) {
1034 if (!(entry
.entryData
[DIR_ENTRY_attributes
] & ATTRIB_DIR
)) {
1038 partition
->cwdCluster
= _FAT_directory_entryGetCluster (partition
, entry
.entryData
);
1043 void _FAT_directory_entryStat (PARTITION
* partition
, DIR_ENTRY
* entry
, struct stat
*st
) {
1044 // Fill in the stat struct
1045 // Some of the values are faked for the sake of compatibility
1046 st
->st_dev
= _FAT_disc_hostType(partition
->disc
); // The device is the 32bit ioType value
1047 st
->st_ino
= (ino_t
)(_FAT_directory_entryGetCluster(partition
, entry
->entryData
)); // The file serial number is the start cluster
1048 st
->st_mode
= (_FAT_directory_isDirectory(entry
) ? S_IFDIR
: S_IFREG
) |
1049 (S_IRUSR
| S_IRGRP
| S_IROTH
) |
1050 (_FAT_directory_isWritable (entry
) ? (S_IWUSR
| S_IWGRP
| S_IWOTH
) : 0); // Mode bits based on dirEntry ATTRIB byte
1051 st
->st_nlink
= 1; // Always one hard link on a FAT file
1052 st
->st_uid
= 1; // Faked for FAT
1053 st
->st_gid
= 2; // Faked for FAT
1054 st
->st_rdev
= st
->st_dev
;
1055 st
->st_size
= u8array_to_u32 (entry
->entryData
, DIR_ENTRY_fileSize
); // File size
1056 st
->st_atime
= _FAT_filetime_to_time_t (
1058 u8array_to_u16 (entry
->entryData
, DIR_ENTRY_aDate
)
1061 st
->st_mtime
= _FAT_filetime_to_time_t (
1062 u8array_to_u16 (entry
->entryData
, DIR_ENTRY_mTime
),
1063 u8array_to_u16 (entry
->entryData
, DIR_ENTRY_mDate
)
1066 st
->st_ctime
= _FAT_filetime_to_time_t (
1067 u8array_to_u16 (entry
->entryData
, DIR_ENTRY_cTime
),
1068 u8array_to_u16 (entry
->entryData
, DIR_ENTRY_cDate
)
1071 st
->st_blksize
= BYTES_PER_READ
; // Prefered file I/O block size
1072 st
->st_blocks
= (st
->st_size
+ BYTES_PER_READ
- 1) / BYTES_PER_READ
; // File size in blocks
1073 st
->st_spare4
[0] = 0;
1074 st
->st_spare4
[1] = 0;