remove trailing spaces
[libfat.git] / source / directory.c
blob18ebe368b0ebba7b7fd5e027703ce560ff3c4985
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 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 const char ILLEGAL_ALIAS_CHARACTERS[] = "\\/:;*?\"<>|&+,=[] ";
80 const char ILLEGAL_LFN_CHARACTERS[] = "\\/:*?\"<>|";
82 /*
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 int i=0;
208 int j=0;
210 destName[0] = '\0';
211 if (entryData[0] != DIR_ENTRY_FREE) {
212 if (entryData[0] == '.') {
213 destName[0] = '.';
214 if (entryData[1] == '.') {
215 destName[1] = '.';
216 destName[2] = '\0';
217 } else {
218 destName[1] = '\0';
220 } else {
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] != ' ') {
227 destName[i++] = '.';
228 for ( j = 0; (j < 3) && (entryData[DIR_ENTRY_extension + j] != ' '); j++) {
229 destName[i++] = entryData[DIR_ENTRY_extension + j];
232 destName[i] = '\0';
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);
243 } else {
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
253 ++ position.offset;
254 if (position.offset == BYTES_PER_READ / DIR_ENTRY_DATA_SIZE) {
255 position.offset = 0;
256 // Increment sector when wrapping
257 ++ position.sector;
258 // But wrap at the end of a cluster
259 if ((position.sector == partition->sectorsPerCluster) && (position.cluster != FAT16_ROOT_DIR_CLUSTER)) {
260 position.sector = 0;
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
269 } else {
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;
279 return true;
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;
288 int lfnPos;
289 uint8_t lfnChkSum, chkSum;
290 bool lfnExists;
291 int i;
293 lfnChkSum = 0;
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;
304 lfnExists = false;
306 found = false;
307 notFound = false;
309 while (!found && !notFound) {
310 if (_FAT_directory_incrementDirEntryPosition (partition, &entryEnd, false) == false) {
311 notFound = true;
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) {
319 // It's an LFN
320 if (entryData[LFN_offset_ordinal] & LFN_DEL) {
321 lfnExists = false;
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
325 lfnExists = true;
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]) {
333 lfnExists = false;
335 if (lfnExists) {
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) {
348 notFound = true;
349 } else if ((entryData[0] != DIR_ENTRY_FREE) && (entryData[0] > 0x20) && !(entryData[DIR_ENTRY_attributes] & ATTRIB_VOL)) {
350 if (lfnExists) {
351 // Calculate file checksum
352 chkSum = 0;
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) {
358 lfnExists = false;
359 entry->filename[0] = '\0';
363 if (lfnExists) {
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?
366 return false;
368 } else {
369 entryStart = entryEnd;
370 _FAT_directory_entryGetAlias (entryData, entry->filename);
372 found = true;
376 // If no file is found, return false
377 if (notFound) {
378 return false;
379 } else {
380 // Fill in the directory entry struct
381 entry->dataStart = entryStart;
382 entry->dataEnd = entryEnd;
383 memcpy (entry->entryData, entryData, DIR_ENTRY_DATA_SIZE);
384 return true;
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);
417 return true;
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;
424 bool finished;
425 ucs2_t lfn[MAX_LFN_LENGTH];
426 int i;
427 int lfnPos;
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);
446 finished = true;
447 } else {
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) {
460 return false;
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)) {
468 return false;
470 } else {
471 // Encode the long file name into a multibyte string
472 if (_FAT_directory_ucs2tombs (entry->filename, lfn, MAX_FILENAME_LENGTH) == (size_t)-1) {
473 return false;
477 return true;
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;
486 uint32_t dirCluster;
487 bool foundFile;
488 char alias[MAX_ALIAS_LENGTH];
489 bool found, notFound;
491 pathPosition = path;
493 found = false;
494 notFound = false;
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) {
506 pathPosition++;
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);
511 found = true;
513 } else {
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);
522 found = true;
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;
530 } else {
531 dirnameLength = strlen(pathPosition);
534 if (dirnameLength > MAX_FILENAME_LENGTH) {
535 // The path is too long to bother with
536 return false;
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)) {
546 found = true;
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)) {
553 found = true;
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
558 found = false;
561 if (!found) {
562 foundFile = _FAT_directory_getNextEntry (partition, entry);
566 if (!foundFile) {
567 // Check that the search didn't get to the end of the directory
568 notFound = true;
569 found = false;
570 } else if ((nextPathPosition == NULL) || (nextPathPosition >= pathEnd)) {
571 // Check that we reached the end of the path
572 found = true;
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) {
578 pathPosition++;
580 // The requested directory was found
581 if (pathPosition >= pathEnd) {
582 found = true;
583 } else {
584 found = false;
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);
597 return true;
598 } else {
599 return false;
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;
607 bool finished;
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)) {
619 finished = true;
623 if (!entryStillValid) {
624 return false;
627 return true;
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
638 gapEnd.offset = 0;
639 gapEnd.sector = 0;
640 gapEnd.cluster = dirCluster;
642 gapStart = gapEnd;
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) {
653 gapStart = gapEnd;
654 -- dirEntryRemain;
655 endOfDirectory = true;
656 } else if (entryData[0] == DIR_ENTRY_FREE) {
657 if (dirEntryRemain == size) {
658 gapStart = gapEnd;
660 -- dirEntryRemain;
661 } else {
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) {
672 return false;
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);
686 -- dirEntryRemain;
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) {
693 return false;
695 } else {
696 entry->dataEnd = gapEnd;
699 return true;
702 static bool _FAT_directory_entryExists (PARTITION* partition, const char* name, uint32_t dirCluster) {
703 DIR_ENTRY tempEntry;
704 bool foundFile;
705 char alias[MAX_ALIAS_LENGTH];
706 size_t dirnameLength;
708 dirnameLength = strnlen(name, MAX_FILENAME_LENGTH);
710 if (dirnameLength >= MAX_FILENAME_LENGTH) {
711 return false;
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)) {
721 return true;
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)) {
728 return true;
730 foundFile = _FAT_directory_getNextEntry (partition, &tempEntry);
732 return false;
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
742 int lfnPos = 0;
743 int aliasPos = 0;
744 wchar_t lfnChar;
745 int oemChar;
746 mbstate_t ps = {0};
747 int bytesUsed = 0;
748 const char* lfnExt;
749 int aliasExtLen;
751 // Strip leading periods
752 while (lfn[lfnPos] == '.') {
753 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);
760 if (bytesUsed < 0) {
761 return -1;
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;
771 lfnPos += bytesUsed;
772 continue;
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;
785 aliasPos++;
786 lfnPos += bytesUsed;
789 if (lfn[lfnPos] != '.' && lfn[lfnPos] != '\0') {
790 // Name was more than 8 characters long
791 lossyConversion = true;
794 // Alias extension
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') {
801 lfnExt++;
802 alias[aliasPos] = '.';
803 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);
807 if (bytesUsed < 0) {
808 return -1;
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;
818 lfnExt += bytesUsed;
819 continue;
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;
832 aliasPos++;
833 lfnExt += bytesUsed;
835 if (*lfnExt != '\0') {
836 // Extension was more than 3 characters long
837 lossyConversion = true;
841 alias[aliasPos] = '\0';
842 if (lossyConversion) {
843 return aliasPos;
844 } else {
845 return 0;
849 bool _FAT_directory_addEntry (PARTITION* partition, DIR_ENTRY* entry, uint32_t dirCluster) {
850 size_t entrySize;
851 uint8_t lfnEntry[DIR_ENTRY_DATA_SIZE];
852 int i,j; // Must be signed for use when decrementing in for loop
853 char *tmpCharPtr;
854 DIR_ENTRY_POSITION curEntryPos;
855 bool entryStillValid;
856 uint8_t aliasCheckSum = 0;
857 char alias [MAX_ALIAS_LENGTH];
858 int aliasLen;
859 int lfnLen;
861 // Make sure the filename is not 0 length
862 if (strnlen (entry->filename, MAX_FILENAME_LENGTH) < 1) {
863 return false;
866 // Make sure the filename is at least a valid LFN
867 lfnLen = _FAT_directory_lfnLength (entry->filename);
868 if (lfnLen < 0) {
869 return false;
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) ;
878 if (i > 0) {
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)) {
888 return false;
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) {
895 // "." entry
896 entry->entryData[0] = '.';
897 entrySize = 1;
898 } else if ( strncmp(entry->filename, "..", MAX_FILENAME_LENGTH) == 0) {
899 // ".." entry
900 entry->entryData[0] = '.';
901 entry->entryData[1] = '.';
902 entrySize = 1;
903 } else {
904 // Normal file name
905 aliasLen = _FAT_directory_createAlias (alias, entry->filename);
906 if (aliasLen < 0) {
907 return false;
908 } else if (aliasLen == 0) {
909 // It's a normal short filename
910 entrySize = 1;
911 } else {
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--;
924 if (i > 0) {
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++) {
933 j = i;
934 tmpCharPtr = alias + MAX_ALIAS_PRI_LENGTH - 1;
935 while (j > 0) {
936 *tmpCharPtr = '0' + (j % 10); // ASCII numeric value
937 tmpCharPtr--;
938 j /= 10;
940 *tmpCharPtr = '~';
941 if (!_FAT_directory_entryExists (partition, alias, dirCluster)) {
942 break;
945 if (i > MAX_NUMERIC_TAIL) {
946 // Couldn't get a valid alias
947 return false;
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];
956 while (j < 8) {
957 entry->entryData[j] = ' ';
958 ++ j;
960 if (alias[i] == '.') {
961 // Copy extension
962 ++ i;
963 while ((alias[i] != '\0') && (j < 11)) {
964 entry->entryData[j] = alias[i];
965 ++ i;
966 ++ j;
969 while (j < 11) {
970 entry->entryData[j] = ' ';
971 ++ 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) {
983 return 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 )
997 if (i > 1) {
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
1004 } else {
1005 u16_to_u8array (lfnEntry, LFN_offset_table[j], 0x0000); // Terminating null character
1007 } else {
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);
1017 } else {
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);
1024 return true;
1027 bool _FAT_directory_chdir (PARTITION* partition, const char* path) {
1028 DIR_ENTRY entry;
1030 if (!_FAT_directory_entryFromPath (partition, &entry, path, NULL)) {
1031 return false;
1034 if (!(entry.entryData[DIR_ENTRY_attributes] & ATTRIB_DIR)) {
1035 return false;
1038 partition->cwdCluster = _FAT_directory_entryGetCluster (partition, entry.entryData);
1040 return true;
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)
1060 st->st_spare1 = 0;
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)
1065 st->st_spare2 = 0;
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)
1070 st->st_spare3 = 0;
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;