case sensitive patch (Extrems)
[libfat.git] / source / directory.c
blob4ec6fcef5abc1fb0b3872cba4c2c28e73bb686de
1 /*
2 directory.c
3 Reading, writing and manipulation of the directory structure on
4 a FAT partition
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.
30 #include <string.h>
31 #include <ctype.h>
32 #include <wchar.h>
33 #include <wctype.h>
34 #include <stdlib.h>
35 #include <stdio.h>
37 #include "directory.h"
38 #include "common.h"
39 #include "partition.h"
40 #include "file_allocation_table.h"
41 #include "bit_ops.h"
42 #include "filetime.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
54 enum LFN_offset {
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 static const int LFN_offset_table[13]={0x01,0x03,0x05,0x07,0x09,0x0E,0x10,0x12,0x14,0x16,0x18,0x1C,0x1E};
76 #define LFN_END 0x40
77 #define LFN_DEL 0x80
79 static const char ILLEGAL_ALIAS_CHARACTERS[] = "\\/:;*?\"<>|&+,=[] ";
80 static 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) {
88 unsigned int i;
89 size_t nameLength;
90 int ucsLength;
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) {
96 return -1;
98 // Make sure it doesn't contain any invalid characters
99 if (strpbrk (name, ILLEGAL_LFN_CHARACTERS) != NULL) {
100 return -1;
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) {
105 return -1;
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) {
111 return -1;
114 // Otherwise it is valid
115 return ucsLength;
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) {
123 mbstate_t ps = {0};
124 wchar_t tempChar;
125 int bytes;
126 size_t count = 0;
128 while (count < len-1 && src != '\0') {
129 bytes = mbrtowc (&tempChar, src, MB_CUR_MAX, &ps);
130 if (bytes > 0) {
131 *dst = (ucs2_t)tempChar;
132 src += bytes;
133 dst++;
134 count++;
135 } else if (bytes == 0) {
136 break;
137 } else {
138 return -1;
141 *dst = '\0';
143 return count;
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) {
151 mbstate_t ps = {0};
152 size_t count = 0;
153 int bytes;
154 char buff[MB_CUR_MAX];
155 int i;
157 while (count < len - 1 && *src != '\0') {
158 bytes = wcrtomb (buff, *src, &ps);
159 if (bytes < 0) {
160 return -1;
162 if (count + bytes < len && bytes > 0) {
163 for (i = 0; i < bytes; i++) {
164 *dst++ = buff[i];
166 src++;
167 count += bytes;
168 } else {
169 break;
172 *dst = L'\0';
174 return count;
178 Case-independent comparison of two multibyte encoded strings
180 static int _FAT_directory_mbsncasecmp (const char* s1, const char* s2, size_t len1) {
181 wchar_t wc1, wc2;
182 mbstate_t ps1 = {0};
183 mbstate_t ps2 = {0};
184 size_t b1 = 0;
185 size_t b2 = 0;
187 if (len1 == 0) {
188 return 0;
191 do {
192 s1 += b1;
193 s2 += b2;
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) {
197 break;
199 len1 -= b1;
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) {
207 char c;
208 bool caseInfo;
209 int i = 0;
210 int j = 0;
212 destName[0] = '\0';
213 if (entryData[0] != DIR_ENTRY_FREE) {
214 if (entryData[0] == '.') {
215 destName[0] = '.';
216 if (entryData[1] == '.') {
217 destName[1] = '.';
218 destName[2] = '\0';
219 } else {
220 destName[1] = '\0';
222 } else {
223 // Copy the filename from the dirEntry to the string
224 caseInfo = entryData[DIR_ENTRY_caseInfo] & CASE_LOWER_BASE;
225 for (i = 0; (i < 8) && (entryData[DIR_ENTRY_name + i] != ' '); i++) {
226 c = entryData[DIR_ENTRY_name + i];
227 destName[i] = (caseInfo ? tolower(c) : c);
229 // Copy the extension from the dirEntry to the string
230 if (entryData[DIR_ENTRY_extension] != ' ') {
231 destName[i++] = '.';
232 caseInfo = entryData[DIR_ENTRY_caseInfo] & CASE_LOWER_EXT;
233 for ( j = 0; (j < 3) && (entryData[DIR_ENTRY_extension + j] != ' '); j++) {
234 c = entryData[DIR_ENTRY_extension + j];
235 destName[i++] = (caseInfo ? tolower(c) : c);
238 destName[i] = '\0';
242 return (destName[0] != '\0');
245 uint32_t _FAT_directory_entryGetCluster (PARTITION* partition, const uint8_t* entryData) {
246 if (partition->filesysType == FS_FAT32) {
247 // Only use high 16 bits of start cluster when we are certain they are correctly defined
248 return u8array_to_u16(entryData,DIR_ENTRY_cluster) | (u8array_to_u16(entryData, DIR_ENTRY_clusterHigh) << 16);
249 } else {
250 return u8array_to_u16(entryData,DIR_ENTRY_cluster);
254 static bool _FAT_directory_incrementDirEntryPosition (PARTITION* partition, DIR_ENTRY_POSITION* entryPosition, bool extendDirectory) {
255 DIR_ENTRY_POSITION position = *entryPosition;
256 uint32_t tempCluster;
258 // Increment offset, wrapping at the end of a sector
259 ++ position.offset;
260 if (position.offset == partition->bytesPerSector / DIR_ENTRY_DATA_SIZE) {
261 position.offset = 0;
262 // Increment sector when wrapping
263 ++ position.sector;
264 // But wrap at the end of a cluster
265 if ((position.sector == partition->sectorsPerCluster) && (position.cluster != FAT16_ROOT_DIR_CLUSTER)) {
266 position.sector = 0;
267 // Move onto the next cluster, making sure there is another cluster to go to
268 tempCluster = _FAT_fat_nextCluster(partition, position.cluster);
269 if (tempCluster == CLUSTER_EOF) {
270 if (extendDirectory) {
271 tempCluster = _FAT_fat_linkFreeClusterCleared (partition, position.cluster);
272 if (!_FAT_fat_isValidCluster(partition, tempCluster)) {
273 return false; // This will only happen if the disc is full
275 } else {
276 return false; // Got to the end of the directory, not extending it
279 position.cluster = tempCluster;
280 } else if ((position.cluster == FAT16_ROOT_DIR_CLUSTER) && (position.sector == (partition->dataStart - partition->rootDirStart))) {
281 return false; // Got to end of root directory, can't extend it
284 *entryPosition = position;
285 return true;
288 bool _FAT_directory_getNextEntry (PARTITION* partition, DIR_ENTRY* entry) {
289 DIR_ENTRY_POSITION entryStart;
290 DIR_ENTRY_POSITION entryEnd;
291 uint8_t entryData[0x20];
292 ucs2_t lfn[MAX_LFN_LENGTH];
293 bool notFound, found;
294 int lfnPos;
295 uint8_t lfnChkSum, chkSum;
296 bool lfnExists;
297 int i;
299 lfnChkSum = 0;
301 entryStart = entry->dataEnd;
303 // Make sure we are using the correct root directory, in case of FAT32
304 if (entryStart.cluster == FAT16_ROOT_DIR_CLUSTER) {
305 entryStart.cluster = partition->rootDirCluster;
308 entryEnd = entryStart;
310 lfnExists = false;
312 found = false;
313 notFound = false;
315 while (!found && !notFound) {
316 if (_FAT_directory_incrementDirEntryPosition (partition, &entryEnd, false) == false) {
317 notFound = true;
320 _FAT_cache_readPartialSector (partition->cache, entryData,
321 _FAT_fat_clusterToSector(partition, entryEnd.cluster) + entryEnd.sector,
322 entryEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE);
324 if (entryData[DIR_ENTRY_attributes] == ATTRIB_LFN) {
325 // It's an LFN
326 if (entryData[LFN_offset_ordinal] & LFN_DEL) {
327 lfnExists = false;
328 } else if (entryData[LFN_offset_ordinal] & LFN_END) {
329 // Last part of LFN, make sure it isn't deleted using previous if(Thanks MoonLight)
330 entryStart = entryEnd; // This is the start of a directory entry
331 lfnExists = true;
332 lfnPos = (entryData[LFN_offset_ordinal] & ~LFN_END) * 13;
333 if (lfnPos > MAX_LFN_LENGTH - 1) {
334 lfnPos = MAX_LFN_LENGTH - 1;
336 lfn[lfnPos] = '\0'; // Set end of lfn to null character
337 lfnChkSum = entryData[LFN_offset_checkSum];
339 if (lfnChkSum != entryData[LFN_offset_checkSum]) {
340 lfnExists = false;
342 if (lfnExists) {
343 lfnPos = ((entryData[LFN_offset_ordinal] & ~LFN_END) - 1) * 13;
344 if (lfnPos > LAST_LFN_POS) {
345 // Force it within the buffer. Will corrupt the filename but prevent buffer overflows
346 lfnPos = LAST_LFN_POS;
348 for (i = 0; i < 13; i++) {
349 lfn[lfnPos + i] = entryData[LFN_offset_table[i]] | (entryData[LFN_offset_table[i]+1] << 8);
352 } else if (entryData[DIR_ENTRY_attributes] & ATTRIB_VOL) {
353 // This is a volume name, don't bother with it
354 } else if (entryData[0] == DIR_ENTRY_LAST) {
355 notFound = true;
356 } else if ((entryData[0] != DIR_ENTRY_FREE) && (entryData[0] > 0x20) && !(entryData[DIR_ENTRY_attributes] & ATTRIB_VOL)) {
357 if (lfnExists) {
358 // Calculate file checksum
359 chkSum = 0;
360 for (i=0; i < 11; i++) {
361 // NOTE: The operation is an unsigned char rotate right
362 chkSum = ((chkSum & 1) ? 0x80 : 0) + (chkSum >> 1) + entryData[i];
364 if (chkSum != lfnChkSum) {
365 lfnExists = false;
366 entry->filename[0] = '\0';
370 if (lfnExists) {
371 if (_FAT_directory_ucs2tombs (entry->filename, lfn, MAX_FILENAME_LENGTH) == (size_t)-1) {
372 // Failed to convert the file name to UTF-8. Maybe the wrong locale is set?
373 return false;
375 } else {
376 entryStart = entryEnd;
377 _FAT_directory_entryGetAlias (entryData, entry->filename);
379 found = true;
383 // If no file is found, return false
384 if (notFound) {
385 return false;
386 } else {
387 // Fill in the directory entry struct
388 entry->dataStart = entryStart;
389 entry->dataEnd = entryEnd;
390 memcpy (entry->entryData, entryData, DIR_ENTRY_DATA_SIZE);
391 return true;
395 bool _FAT_directory_getFirstEntry (PARTITION* partition, DIR_ENTRY* entry, uint32_t dirCluster) {
396 entry->dataStart.cluster = dirCluster;
397 entry->dataStart.sector = 0;
398 entry->dataStart.offset = -1; // Start before the beginning of the directory
400 entry->dataEnd = entry->dataStart;
402 return _FAT_directory_getNextEntry (partition, entry);
405 bool _FAT_directory_getRootEntry (PARTITION* partition, DIR_ENTRY* entry) {
406 entry->dataStart.cluster = 0;
407 entry->dataStart.sector = 0;
408 entry->dataStart.offset = 0;
410 entry->dataEnd = entry->dataStart;
412 memset (entry->filename, '\0', MAX_FILENAME_LENGTH);
413 entry->filename[0] = '.';
415 memset (entry->entryData, 0, DIR_ENTRY_DATA_SIZE);
416 memset (entry->entryData, ' ', 11);
417 entry->entryData[0] = '.';
419 entry->entryData[DIR_ENTRY_attributes] = ATTRIB_DIR;
421 u16_to_u8array (entry->entryData, DIR_ENTRY_cluster, partition->rootDirCluster);
422 u16_to_u8array (entry->entryData, DIR_ENTRY_clusterHigh, partition->rootDirCluster >> 16);
424 return true;
427 bool _FAT_directory_getVolumeLabel (PARTITION* partition, char *label) {
428 DIR_ENTRY entry;
429 DIR_ENTRY_POSITION entryEnd;
430 uint8_t entryData[DIR_ENTRY_DATA_SIZE];
431 int i;
432 bool end;
434 _FAT_directory_getRootEntry(partition, &entry);
436 entryEnd = entry.dataEnd;
438 // Make sure we are using the correct root directory, in case of FAT32
439 if (entryEnd.cluster == FAT16_ROOT_DIR_CLUSTER) {
440 entryEnd.cluster = partition->rootDirCluster;
443 label[0]='\0';
444 label[11]='\0';
445 end = false;
446 //this entry should be among the first 3 entries in the root directory table, if not, then system can have trouble displaying the right volume label
447 while(!end) {
448 if(!_FAT_cache_readPartialSector (partition->cache, entryData,
449 _FAT_fat_clusterToSector(partition, entryEnd.cluster) + entryEnd.sector,
450 entryEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE))
451 { //error reading
452 return false;
455 if (entryData[DIR_ENTRY_attributes] == ATTRIB_VOL && entryData[0] != DIR_ENTRY_FREE) {
456 for (i = 0; i < 11; i++) {
457 label[i] = entryData[DIR_ENTRY_name + i];
459 return true;
460 } else if (entryData[0] == DIR_ENTRY_LAST) {
461 end = true;
464 if (_FAT_directory_incrementDirEntryPosition (partition, &entryEnd, false) == false) {
465 end = true;
468 return false;
471 bool _FAT_directory_entryFromPosition (PARTITION* partition, DIR_ENTRY* entry) {
472 DIR_ENTRY_POSITION entryStart = entry->dataStart;
473 DIR_ENTRY_POSITION entryEnd = entry->dataEnd;
474 bool entryStillValid;
475 bool finished;
476 ucs2_t lfn[MAX_LFN_LENGTH];
477 int i;
478 int lfnPos;
479 uint8_t entryData[DIR_ENTRY_DATA_SIZE];
481 memset (entry->filename, '\0', MAX_FILENAME_LENGTH);
483 // Create an empty directory entry to overwrite the old ones with
484 for ( entryStillValid = true, finished = false;
485 entryStillValid && !finished;
486 entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &entryStart, false))
488 _FAT_cache_readPartialSector (partition->cache, entryData,
489 _FAT_fat_clusterToSector(partition, entryStart.cluster) + entryStart.sector,
490 entryStart.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE);
492 if ((entryStart.cluster == entryEnd.cluster)
493 && (entryStart.sector == entryEnd.sector)
494 && (entryStart.offset == entryEnd.offset)) {
495 // Copy the entry data and stop, since this is the last section of the directory entry
496 memcpy (entry->entryData, entryData, DIR_ENTRY_DATA_SIZE);
497 finished = true;
498 } else {
499 // Copy the long file name data
500 lfnPos = ((entryData[LFN_offset_ordinal] & ~LFN_END) - 1) * 13;
501 if (lfnPos > LAST_LFN_POS) {
502 lfnPos = LAST_LFN_POS_CORRECTION;
504 for (i = 0; i < 13; i++) {
505 lfn[lfnPos + i] = entryData[LFN_offset_table[i]] | (entryData[LFN_offset_table[i]+1] << 8);
510 if (!entryStillValid) {
511 return false;
514 if ((entryStart.cluster == entryEnd.cluster)
515 && (entryStart.sector == entryEnd.sector)
516 && (entryStart.offset == entryEnd.offset)) {
517 // Since the entry doesn't have a long file name, extract the short filename
518 if (!_FAT_directory_entryGetAlias (entry->entryData, entry->filename)) {
519 return false;
521 } else {
522 // Encode the long file name into a multibyte string
523 if (_FAT_directory_ucs2tombs (entry->filename, lfn, MAX_FILENAME_LENGTH) == (size_t)-1) {
524 return false;
528 return true;
533 bool _FAT_directory_entryFromPath (PARTITION* partition, DIR_ENTRY* entry, const char* path, const char* pathEnd) {
534 size_t dirnameLength;
535 const char* pathPosition;
536 const char* nextPathPosition;
537 uint32_t dirCluster;
538 bool foundFile;
539 char alias[MAX_ALIAS_LENGTH];
540 bool found, notFound;
542 pathPosition = path;
544 found = false;
545 notFound = false;
547 if (pathEnd == NULL) {
548 // Set pathEnd to the end of the path string
549 pathEnd = strchr (path, '\0');
552 if (pathPosition[0] == DIR_SEPARATOR) {
553 // Start at root directory
554 dirCluster = partition->rootDirCluster;
555 // Consume separator(s)
556 while (pathPosition[0] == DIR_SEPARATOR) {
557 pathPosition++;
559 // If the path is only specifying a directory in the form of "/" return it
560 if (pathPosition >= pathEnd) {
561 _FAT_directory_getRootEntry (partition, entry);
562 found = true;
564 } else {
565 // Start in current working directory
566 dirCluster = partition->cwdCluster;
569 // If the path is only specifying a directory in the form "."
570 // and this is the root directory, return it
571 if ((dirCluster == partition->rootDirCluster) && (strcmp(".", pathPosition) == 0)) {
572 _FAT_directory_getRootEntry (partition, entry);
573 found = true;
576 while (!found && !notFound) {
577 // Get the name of the next required subdirectory within the path
578 nextPathPosition = strchr (pathPosition, DIR_SEPARATOR);
579 if (nextPathPosition != NULL) {
580 dirnameLength = nextPathPosition - pathPosition;
581 } else {
582 dirnameLength = strlen(pathPosition);
585 if (dirnameLength > MAX_FILENAME_LENGTH) {
586 // The path is too long to bother with
587 return false;
590 // Look for the directory within the path
591 foundFile = _FAT_directory_getFirstEntry (partition, entry, dirCluster);
593 while (foundFile && !found && !notFound) { // It hasn't already found the file
594 // Check if the filename matches
595 if ((dirnameLength == strnlen(entry->filename, MAX_FILENAME_LENGTH))
596 && (_FAT_directory_mbsncasecmp(pathPosition, entry->filename, dirnameLength) == 0)) {
597 found = true;
600 // Check if the alias matches
601 _FAT_directory_entryGetAlias (entry->entryData, alias);
602 if ((dirnameLength == strnlen(alias, MAX_ALIAS_LENGTH))
603 && (strncasecmp(pathPosition, alias, dirnameLength) == 0)) {
604 found = true;
607 if (found && !(entry->entryData[DIR_ENTRY_attributes] & ATTRIB_DIR) && (nextPathPosition != NULL)) {
608 // Make sure that we aren't trying to follow a file instead of a directory in the path
609 found = false;
612 if (!found) {
613 foundFile = _FAT_directory_getNextEntry (partition, entry);
617 if (!foundFile) {
618 // Check that the search didn't get to the end of the directory
619 notFound = true;
620 found = false;
621 } else if ((nextPathPosition == NULL) || (nextPathPosition >= pathEnd)) {
622 // Check that we reached the end of the path
623 found = true;
624 } else if (entry->entryData[DIR_ENTRY_attributes] & ATTRIB_DIR) {
625 dirCluster = _FAT_directory_entryGetCluster (partition, entry->entryData);
626 pathPosition = nextPathPosition;
627 // Consume separator(s)
628 while (pathPosition[0] == DIR_SEPARATOR) {
629 pathPosition++;
631 // The requested directory was found
632 if (pathPosition >= pathEnd) {
633 found = true;
634 } else {
635 found = false;
640 if (found && !notFound) {
641 if (partition->filesysType == FS_FAT32 && (entry->entryData[DIR_ENTRY_attributes] & ATTRIB_DIR) &&
642 _FAT_directory_entryGetCluster (partition, entry->entryData) == CLUSTER_ROOT)
644 // On FAT32 it should specify an actual cluster for the root entry,
645 // not cluster 0 as on FAT16
646 _FAT_directory_getRootEntry (partition, entry);
648 return true;
649 } else {
650 return false;
654 bool _FAT_directory_removeEntry (PARTITION* partition, DIR_ENTRY* entry) {
655 DIR_ENTRY_POSITION entryStart = entry->dataStart;
656 DIR_ENTRY_POSITION entryEnd = entry->dataEnd;
657 bool entryStillValid;
658 bool finished;
659 uint8_t entryData[DIR_ENTRY_DATA_SIZE];
661 // Create an empty directory entry to overwrite the old ones with
662 for ( entryStillValid = true, finished = false;
663 entryStillValid && !finished;
664 entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &entryStart, false))
666 _FAT_cache_readPartialSector (partition->cache, entryData, _FAT_fat_clusterToSector(partition, entryStart.cluster) + entryStart.sector, entryStart.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE);
667 entryData[0] = DIR_ENTRY_FREE;
668 _FAT_cache_writePartialSector (partition->cache, entryData, _FAT_fat_clusterToSector(partition, entryStart.cluster) + entryStart.sector, entryStart.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE);
669 if ((entryStart.cluster == entryEnd.cluster) && (entryStart.sector == entryEnd.sector) && (entryStart.offset == entryEnd.offset)) {
670 finished = true;
674 if (!entryStillValid) {
675 return false;
678 return true;
681 static bool _FAT_directory_findEntryGap (PARTITION* partition, DIR_ENTRY* entry, uint32_t dirCluster, size_t size) {
682 DIR_ENTRY_POSITION gapStart;
683 DIR_ENTRY_POSITION gapEnd;
684 uint8_t entryData[DIR_ENTRY_DATA_SIZE];
685 size_t dirEntryRemain;
686 bool endOfDirectory, entryStillValid;
688 // Scan Dir for free entry
689 gapEnd.offset = 0;
690 gapEnd.sector = 0;
691 gapEnd.cluster = dirCluster;
693 gapStart = gapEnd;
695 entryStillValid = true;
696 dirEntryRemain = size;
697 endOfDirectory = false;
699 while (entryStillValid && !endOfDirectory && (dirEntryRemain > 0)) {
700 _FAT_cache_readPartialSector (partition->cache, entryData,
701 _FAT_fat_clusterToSector(partition, gapEnd.cluster) + gapEnd.sector,
702 gapEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE);
703 if (entryData[0] == DIR_ENTRY_LAST) {
704 gapStart = gapEnd;
705 -- dirEntryRemain;
706 endOfDirectory = true;
707 } else if (entryData[0] == DIR_ENTRY_FREE) {
708 if (dirEntryRemain == size) {
709 gapStart = gapEnd;
711 -- dirEntryRemain;
712 } else {
713 dirEntryRemain = size;
716 if (!endOfDirectory && (dirEntryRemain > 0)) {
717 entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &gapEnd, true);
721 // Make sure the scanning didn't fail
722 if (!entryStillValid) {
723 return false;
726 // Save the start entry, since we know it is valid
727 entry->dataStart = gapStart;
729 if (endOfDirectory) {
730 memset (entryData, DIR_ENTRY_LAST, DIR_ENTRY_DATA_SIZE);
731 dirEntryRemain += 1; // Increase by one to take account of End Of Directory Marker
732 while ((dirEntryRemain > 0) && entryStillValid) {
733 // Get the gapEnd before incrementing it, so the second to last one is saved
734 entry->dataEnd = gapEnd;
735 // Increment gapEnd, moving onto the next entry
736 entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &gapEnd, true);
737 -- dirEntryRemain;
738 // Fill the entry with blanks
739 _FAT_cache_writePartialSector (partition->cache, entryData,
740 _FAT_fat_clusterToSector(partition, gapEnd.cluster) + gapEnd.sector,
741 gapEnd.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE);
743 if (!entryStillValid) {
744 return false;
746 } else {
747 entry->dataEnd = gapEnd;
750 return true;
753 static bool _FAT_directory_entryExists (PARTITION* partition, const char* name, uint32_t dirCluster) {
754 DIR_ENTRY tempEntry;
755 bool foundFile;
756 char alias[MAX_ALIAS_LENGTH];
757 size_t dirnameLength;
759 dirnameLength = strnlen(name, MAX_FILENAME_LENGTH);
761 if (dirnameLength >= MAX_FILENAME_LENGTH) {
762 return false;
765 // Make sure the entry doesn't already exist
766 foundFile = _FAT_directory_getFirstEntry (partition, &tempEntry, dirCluster);
768 while (foundFile) { // It hasn't already found the file
769 // Check if the filename matches
770 if ((dirnameLength == strnlen(tempEntry.filename, MAX_FILENAME_LENGTH))
771 && (_FAT_directory_mbsncasecmp(name, tempEntry.filename, dirnameLength) == 0)) {
772 return true;
775 // Check if the alias matches
776 _FAT_directory_entryGetAlias (tempEntry.entryData, alias);
777 if ((strncasecmp(name, alias, MAX_ALIAS_LENGTH) == 0)) {
778 return true;
780 foundFile = _FAT_directory_getNextEntry (partition, &tempEntry);
782 return false;
786 Creates an alias for a long file name. If the alias is not an exact match for the
787 filename, it returns the number of characters in the alias. If the two names match,
788 it returns 0. If there was an error, it returns -1.
790 static int _FAT_directory_createAlias (char* alias, const char* lfn) {
791 bool lossyConversion = false; // Set when the alias had to be modified to be valid
792 int lfnPos = 0;
793 int aliasPos = 0;
794 wchar_t lfnChar;
795 int oemChar;
796 mbstate_t ps = {0};
797 int bytesUsed = 0;
798 const char* lfnExt;
799 int aliasExtLen;
801 // Strip leading periods
802 while (lfn[lfnPos] == '.') {
803 lfnPos ++;
804 lossyConversion = true;
807 // Primary portion of alias
808 while (aliasPos < 8 && lfn[lfnPos] != '.' && lfn[lfnPos] != '\0') {
809 bytesUsed = mbrtowc(&lfnChar, lfn + lfnPos, MAX_FILENAME_LENGTH - lfnPos, &ps);
810 if (bytesUsed < 0) {
811 return -1;
813 oemChar = wctob(towupper((wint_t)lfnChar));
814 if (wctob((wint_t)lfnChar) != oemChar) {
815 // Case of letter was changed
816 lossyConversion = true;
818 if (oemChar == ' ') {
819 // Skip spaces in filename
820 lossyConversion = true;
821 lfnPos += bytesUsed;
822 continue;
824 if (oemChar == EOF) {
825 oemChar = '_'; // Replace unconvertable characters with underscores
826 lossyConversion = true;
828 if (strchr (ILLEGAL_ALIAS_CHARACTERS, oemChar) != NULL) {
829 // Invalid Alias character
830 oemChar = '_'; // Replace illegal characters with underscores
831 lossyConversion = true;
834 alias[aliasPos] = (char)oemChar;
835 aliasPos++;
836 lfnPos += bytesUsed;
839 if (lfn[lfnPos] != '.' && lfn[lfnPos] != '\0') {
840 // Name was more than 8 characters long
841 lossyConversion = true;
844 // Alias extension
845 lfnExt = strrchr (lfn, '.');
846 if (lfnExt != NULL && lfnExt != strchr (lfn, '.')) {
847 // More than one period in name
848 lossyConversion = true;
850 if (lfnExt != NULL && lfnExt[1] != '\0') {
851 lfnExt++;
852 alias[aliasPos] = '.';
853 aliasPos++;
854 memset (&ps, 0, sizeof(ps));
855 for (aliasExtLen = 0; aliasExtLen < MAX_ALIAS_EXT_LENGTH && *lfnExt != '\0'; aliasExtLen++) {
856 bytesUsed = mbrtowc(&lfnChar, lfnExt, MAX_FILENAME_LENGTH - lfnPos, &ps);
857 if (bytesUsed < 0) {
858 return -1;
860 oemChar = wctob(towupper((wint_t)lfnChar));
861 if (wctob((wint_t)lfnChar) != oemChar) {
862 // Case of letter was changed
863 lossyConversion = true;
865 if (oemChar == ' ') {
866 // Skip spaces in alias
867 lossyConversion = true;
868 lfnExt += bytesUsed;
869 continue;
871 if (oemChar == EOF) {
872 oemChar = '_'; // Replace unconvertable characters with underscores
873 lossyConversion = true;
875 if (strchr (ILLEGAL_ALIAS_CHARACTERS, oemChar) != NULL) {
876 // Invalid Alias character
877 oemChar = '_'; // Replace illegal characters with underscores
878 lossyConversion = true;
881 alias[aliasPos] = (char)oemChar;
882 aliasPos++;
883 lfnExt += bytesUsed;
885 if (*lfnExt != '\0') {
886 // Extension was more than 3 characters long
887 lossyConversion = true;
891 alias[aliasPos] = '\0';
892 if (lossyConversion) {
893 return aliasPos;
894 } else {
895 return 0;
899 bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, uint32_t dirCluster) {
900 size_t entrySize;
901 uint8_t lfnEntry[DIR_ENTRY_DATA_SIZE];
902 int i,j; // Must be signed for use when decrementing in for loop
903 char *tmpCharPtr;
904 DIR_ENTRY_POSITION curEntryPos;
905 bool entryStillValid;
906 uint8_t aliasCheckSum = 0;
907 char alias [MAX_ALIAS_LENGTH];
908 int aliasLen;
909 int lfnLen;
911 // Make sure the filename is not 0 length
912 if (strnlen (entry->filename, MAX_FILENAME_LENGTH) < 1) {
913 return false;
916 // Make sure the filename is at least a valid LFN
917 lfnLen = _FAT_directory_lfnLength (entry->filename);
918 if (lfnLen < 0) {
919 return false;
922 // Remove trailing spaces
923 for (i = strlen (entry->filename) - 1; (i > 0) && (entry->filename[i] == ' '); --i) {
924 entry->filename[i] = '\0';
926 // Remove leading spaces
927 for (i = 0; (i < (int)strlen (entry->filename)) && (entry->filename[i] == ' '); ++i) ;
928 if (i > 0) {
929 memmove (entry->filename, entry->filename + i, strlen (entry->filename + i));
932 // Remove junk in filename
933 i = strlen (entry->filename);
934 memset (entry->filename + i, '\0', MAX_FILENAME_LENGTH - i);
936 // Make sure the entry doesn't already exist
937 if (_FAT_directory_entryExists (partition, entry->filename, dirCluster)) {
938 return false;
941 // Clear out alias, so we can generate a new one
942 memset (entry->entryData, ' ', 11);
944 if ( strncmp(entry->filename, ".", MAX_FILENAME_LENGTH) == 0) {
945 // "." entry
946 entry->entryData[0] = '.';
947 entrySize = 1;
948 } else if ( strncmp(entry->filename, "..", MAX_FILENAME_LENGTH) == 0) {
949 // ".." entry
950 entry->entryData[0] = '.';
951 entry->entryData[1] = '.';
952 entrySize = 1;
953 } else {
954 // Normal file name
955 aliasLen = _FAT_directory_createAlias (alias, entry->filename);
956 if (aliasLen < 0) {
957 return false;
958 } else if (aliasLen == 0) {
959 // It's a normal short filename
960 entrySize = 1;
961 } else {
962 // It's a long filename with an alias
963 entrySize = ((lfnLen + LFN_ENTRY_LENGTH - 1) / LFN_ENTRY_LENGTH) + 1;
965 // Generate full alias for all cases except when the alias is simply an upper case version of the LFN
966 // and there isn't already a file with that name
967 if (strncasecmp (alias, entry->filename, MAX_ALIAS_LENGTH) != 0 ||
968 _FAT_directory_entryExists (partition, alias, dirCluster))
970 // expand primary part to 8 characters long by padding the end with underscores
971 i = MAX_ALIAS_PRI_LENGTH - 1;
972 // Move extension to last 3 characters
973 while (alias[i] != '.' && i > 0) i--;
974 if (i > 0) {
975 j = MAX_ALIAS_LENGTH - MAX_ALIAS_EXT_LENGTH - 2; // 1 char for '.', one for NUL, 3 for extension
976 memmove (alias + j, alias + i, strlen(alias) - i);
977 // Pad primary component
978 memset (alias + i, '_', j - i);
979 alias[MAX_ALIAS_LENGTH-1]=0;
982 // Generate numeric tail
983 for (i = 1; i <= MAX_NUMERIC_TAIL; i++) {
984 j = i;
985 tmpCharPtr = alias + MAX_ALIAS_PRI_LENGTH - 1;
986 while (j > 0) {
987 *tmpCharPtr = '0' + (j % 10); // ASCII numeric value
988 tmpCharPtr--;
989 j /= 10;
991 *tmpCharPtr = '~';
992 if (!_FAT_directory_entryExists (partition, alias, dirCluster)) {
993 break;
996 if (i > MAX_NUMERIC_TAIL) {
997 // Couldn't get a valid alias
998 return false;
1003 // Copy alias or short file name into directory entry data
1004 for (i = 0, j = 0; (j < 8) && (alias[i] != '.') && (alias[i] != '\0'); i++, j++) {
1005 entry->entryData[j] = alias[i];
1007 while (j < 8) {
1008 entry->entryData[j] = ' ';
1009 ++ j;
1011 if (alias[i] == '.') {
1012 // Copy extension
1013 ++ i;
1014 while ((alias[i] != '\0') && (j < 11)) {
1015 entry->entryData[j] = alias[i];
1016 ++ i;
1017 ++ j;
1020 while (j < 11) {
1021 entry->entryData[j] = ' ';
1022 ++ j;
1025 // Generate alias checksum
1026 for (i=0; i < ALIAS_ENTRY_LENGTH; i++) {
1027 // NOTE: The operation is an unsigned char rotate right
1028 aliasCheckSum = ((aliasCheckSum & 1) ? 0x80 : 0) + (aliasCheckSum >> 1) + entry->entryData[i];
1032 // Find or create space for the entry
1033 if (_FAT_directory_findEntryGap (partition, entry, dirCluster, entrySize) == false) {
1034 return false;
1037 // Write out directory entry
1038 curEntryPos = entry->dataStart;
1041 // lfn is only pushed onto the stack here, reducing overall stack usage
1042 ucs2_t lfn[MAX_LFN_LENGTH] = {0};
1043 _FAT_directory_mbstoucs2 (lfn, entry->filename, MAX_LFN_LENGTH);
1045 for (entryStillValid = true, i = entrySize; entryStillValid && i > 0;
1046 entryStillValid = _FAT_directory_incrementDirEntryPosition (partition, &curEntryPos, false), -- i )
1048 if (i > 1) {
1049 // Long filename entry
1050 lfnEntry[LFN_offset_ordinal] = (i - 1) | ((size_t)i == entrySize ? LFN_END : 0);
1051 for (j = 0; j < 13; j++) {
1052 if (lfn [(i - 2) * 13 + j] == '\0') {
1053 if ((j > 1) && (lfn [(i - 2) * 13 + (j-1)] == '\0')) {
1054 u16_to_u8array (lfnEntry, LFN_offset_table[j], 0xffff); // Padding
1055 } else {
1056 u16_to_u8array (lfnEntry, LFN_offset_table[j], 0x0000); // Terminating null character
1058 } else {
1059 u16_to_u8array (lfnEntry, LFN_offset_table[j], lfn [(i - 2) * 13 + j]);
1063 lfnEntry[LFN_offset_checkSum] = aliasCheckSum;
1064 lfnEntry[LFN_offset_flag] = ATTRIB_LFN;
1065 lfnEntry[LFN_offset_reserved1] = 0;
1066 u16_to_u8array (lfnEntry, LFN_offset_reserved2, 0);
1067 _FAT_cache_writePartialSector (partition->cache, lfnEntry, _FAT_fat_clusterToSector(partition, curEntryPos.cluster) + curEntryPos.sector, curEntryPos.offset * DIR_ENTRY_DATA_SIZE, DIR_ENTRY_DATA_SIZE);
1068 } else {
1069 // Alias & file data
1070 _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);
1075 return true;
1078 bool _FAT_directory_chdir (PARTITION* partition, const char* path) {
1079 DIR_ENTRY entry;
1081 if (!_FAT_directory_entryFromPath (partition, &entry, path, NULL)) {
1082 return false;
1085 if (!(entry.entryData[DIR_ENTRY_attributes] & ATTRIB_DIR)) {
1086 return false;
1089 partition->cwdCluster = _FAT_directory_entryGetCluster (partition, entry.entryData);
1091 return true;
1094 void _FAT_directory_entryStat (PARTITION* partition, DIR_ENTRY* entry, struct stat *st) {
1095 // Fill in the stat struct
1096 // Some of the values are faked for the sake of compatibility
1097 st->st_dev = _FAT_disc_hostType(partition->disc); // The device is the 32bit ioType value
1098 st->st_ino = (ino_t)(_FAT_directory_entryGetCluster(partition, entry->entryData)); // The file serial number is the start cluster
1099 st->st_mode = (_FAT_directory_isDirectory(entry) ? S_IFDIR : S_IFREG) |
1100 (S_IRUSR | S_IRGRP | S_IROTH) |
1101 (_FAT_directory_isWritable (entry) ? (S_IWUSR | S_IWGRP | S_IWOTH) : 0); // Mode bits based on dirEntry ATTRIB byte
1102 st->st_nlink = 1; // Always one hard link on a FAT file
1103 st->st_uid = 1; // Faked for FAT
1104 st->st_gid = 2; // Faked for FAT
1105 st->st_rdev = st->st_dev;
1106 st->st_size = u8array_to_u32 (entry->entryData, DIR_ENTRY_fileSize); // File size
1107 st->st_atime = _FAT_filetime_to_time_t (
1109 u8array_to_u16 (entry->entryData, DIR_ENTRY_aDate)
1111 st->st_spare1 = 0;
1112 st->st_mtime = _FAT_filetime_to_time_t (
1113 u8array_to_u16 (entry->entryData, DIR_ENTRY_mTime),
1114 u8array_to_u16 (entry->entryData, DIR_ENTRY_mDate)
1116 st->st_spare2 = 0;
1117 st->st_ctime = _FAT_filetime_to_time_t (
1118 u8array_to_u16 (entry->entryData, DIR_ENTRY_cTime),
1119 u8array_to_u16 (entry->entryData, DIR_ENTRY_cDate)
1121 st->st_spare3 = 0;
1122 st->st_blksize = partition->bytesPerSector; // Prefered file I/O block size
1123 st->st_blocks = (st->st_size + partition->bytesPerSector - 1) / partition->bytesPerSector; // File size in blocks
1124 st->st_spare4[0] = 0;
1125 st->st_spare4[1] = 0;