diff --git a/src/main/io/asyncfatfs/asyncfatfs.c b/src/main/io/asyncfatfs/asyncfatfs.c index ed3f83044a..c5f0d9633c 100644 --- a/src/main/io/asyncfatfs/asyncfatfs.c +++ b/src/main/io/asyncfatfs/asyncfatfs.c @@ -1,3 +1,20 @@ +/* + * This file is part of Cleanflight. + * + * Cleanflight is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Cleanflight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Cleanflight. If not, see . + */ + /** * This is a FAT16/FAT32 filesystem for SD cards which uses asynchronous operations: The caller need never wait * for the SD card to be ready. @@ -27,6 +44,12 @@ #include "fat_standard.h" #include "drivers/sdcard.h" +#ifdef AFATFS_DEBUG + #define ONLY_EXPOSE_FOR_TESTING +#else + #define ONLY_EXPOSE_FOR_TESTING static +#endif + #define AFATFS_NUM_CACHE_SECTORS 8 // FAT filesystems are allowed to differ from these parameters, but we choose not to support those weird filesystems: @@ -35,6 +58,9 @@ #define AFATFS_MAX_OPEN_FILES 3 +#define AFATFS_DEFAULT_FILE_DATE FAT_MAKE_DATE(2015, 12, 01) +#define AFATFS_DEFAULT_FILE_TIME FAT_MAKE_TIME(00, 00, 00) + #define AFATFS_FILES_PER_DIRECTORY_SECTOR (AFATFS_SECTOR_SIZE / sizeof(fatDirectoryEntry_t)) #define AFATFS_FAT32_FAT_ENTRIES_PER_SECTOR (AFATFS_SECTOR_SIZE / sizeof(uint32_t)) @@ -78,6 +104,12 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) +typedef enum { + AFATFS_SAVE_DIRECTORY_NORMAL, + AFATFS_SAVE_DIRECTORY_FOR_CLOSE, + AFATFS_SAVE_DIRECTORY_DELETED, +} afatfsSaveDirectoryEntryMode_e; + typedef enum { AFATFS_CACHE_STATE_EMPTY, AFATFS_CACHE_STATE_IN_SYNC, @@ -190,6 +222,7 @@ typedef struct afatfsCreateFile_t { afatfsFileCallback_t callback; uint8_t phase; + uint8_t filename[FAT_FILENAME_LENGTH]; } afatfsCreateFile_t; typedef struct afatfsSeek_t { @@ -200,16 +233,16 @@ typedef struct afatfsSeek_t { typedef enum { AFATFS_APPEND_SUPERCLUSTER_PHASE_INIT = 0, - AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FAT, AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FREEFILE_DIRECTORY, + AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FAT, AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FILE_DIRECTORY, } afatfsAppendSuperclusterPhase_e; typedef struct afatfsAppendSupercluster_t { - afatfsAppendSuperclusterPhase_e phase; uint32_t previousCluster; uint32_t fatRewriteStartCluster; uint32_t fatRewriteEndCluster; + afatfsAppendSuperclusterPhase_e phase; } afatfsAppendSupercluster_t; typedef enum { @@ -223,9 +256,9 @@ typedef enum { } afatfsAppendFreeClusterPhase_e; typedef struct afatfsAppendFreeCluster_t { - afatfsAppendFreeClusterPhase_e phase; uint32_t previousCluster; uint32_t searchCluster; + afatfsAppendFreeClusterPhase_e phase; } afatfsAppendFreeCluster_t; typedef enum { @@ -241,6 +274,7 @@ typedef struct afatfsExtendSubdirectory_t { afatfsAppendFreeCluster_t appendFreeCluster; afatfsExtendSubdirectoryPhase_e phase; + uint32_t parentDirectoryCluster; afatfsFileCallback_t callback; } afatfsExtendSubdirectory_t; @@ -311,6 +345,30 @@ typedef struct afatfsFile_t { // The byte offset of the cursor within the file uint32_t cursorOffset; + /* The file size in bytes as seen by users of the filesystem (the exact length of the file they've written). + * + * This is only used by users of the filesystem, not us, so it only needs to be up to date for fseek() (to clip + * seeks to the EOF), fread(), feof(), and fclose() (which writes the logicalSize to the directory). + * + * It becomes out of date when we fwrite() to extend the length of the file. In this situation, feof() is properly + * true, so we don't have to update the logicalSize for fread() or feof() to get the correct result. We only need + * to update it when we seek backwards (so we don't forget the logical EOF position), or fclose(). + */ + uint32_t logicalSize; + + /* The allocated size in bytes based on how many clusters have been assigned to the file. Always a multiple of + * the cluster size. + * + * This is an underestimate for existing files, because we don't bother to check precisely how long the chain is + * at the time the file is opened (it might be longer than needed to contain the logical size), but assuming the + * filesystem metadata is correct, it should always be at least as many clusters as needed to contain logicalSize. + * + * Since this is an estimate, we only use it to exaggerate the filesize in the directory entry of a file that is + * currently being written (so that the final cluster of the file will be entirely readable if power is lost before + * we can could update the directory entry with a new logicalSize). + */ + uint32_t physicalSize; + /* * The cluster that the file pointer is currently within. When seeking to the end of the file, this will be * set to zero. @@ -323,6 +381,7 @@ typedef struct afatfsFile_t { uint32_t cursorPreviousCluster; uint8_t mode; // A combination of AFATFS_FILE_MODE_* flags + uint8_t attrib; // FAT directory entry attributes for this file /* We hold on to one sector entry in the cache and remember its index here. The cache is invalidated when we * seek across a sector boundary. This allows fwrite() to complete faster because it doesn't need to check the @@ -334,8 +393,9 @@ typedef struct afatfsFile_t { // The position of our directory entry on the disk (so we can update it without consulting a parent directory file) afatfsDirEntryPointer_t directoryEntryPos; - // A copy of the file's FAT directory entry (especially fileSize and first cluster details) - fatDirectoryEntry_t directoryEntry; + + // The first cluster number of the file, or 0 if this file is empty + uint32_t firstCluster; // State for a queued operation on the file struct afatfsOperation_t operation; @@ -430,12 +490,6 @@ static bool isPowerOfTwo(unsigned int x) return ((x != 0) && ((x & (~x + 1)) == x)); } -static uint32_t afatfs_firstCluster(afatfsFilePtr_t file) -{ - // Take care that the uint16_t doesn't get promoted to signed int... - return ((uint32_t) file->directoryEntry.firstClusterHigh << 16) | (file->directoryEntry.firstClusterLow); -} - /** * Check for conditions that should always be true (and if otherwise mean a bug or a corrupt filesystem). * @@ -471,7 +525,8 @@ static uint32_t afatfs_fatEntriesPerSector() /** * Size of a FAT cluster in bytes */ - static uint32_t afatfs_clusterSize() +ONLY_EXPOSE_FOR_TESTING +uint32_t afatfs_clusterSize() { return afatfs.sectorsPerCluster * AFATFS_SECTOR_SIZE; } @@ -1028,6 +1083,14 @@ static afatfsOperationStatus_e afatfs_FATSetNextCluster(uint32_t startCluster, u return result; } +/** + * Bring the logical filesize up to date with the current cursor position. + */ +static void afatfs_fileUpdateFilesize(afatfsFile_t *file) +{ + file->logicalSize = MAX(file->logicalSize, file->cursorOffset); +} + static void afatfs_fileUnlockCacheSector(afatfsFilePtr_t file) { if (file->writeLockedCacheIndex != -1) { @@ -1095,8 +1158,8 @@ static afatfsFindClusterStatus_e afatfs_findClusterWithCondition(afatfsClusterSe #ifdef AFATFS_USE_FREEFILE // If we're looking inside the freefile, we won't find any free clusters! Skip it! - if (afatfs.freeFile.directoryEntry.fileSize > 0 && *cluster == afatfs_firstCluster(&afatfs.freeFile)) { - *cluster += (afatfs.freeFile.directoryEntry.fileSize + afatfs_clusterSize() - 1) / afatfs_clusterSize(); + if (afatfs.freeFile.logicalSize > 0 && *cluster == afatfs.freeFile.firstCluster) { + *cluster += (afatfs.freeFile.logicalSize + afatfs_clusterSize() - 1) / afatfs_clusterSize(); // Maintain alignment *cluster = roundUpTo(*cluster, jump); @@ -1170,7 +1233,7 @@ static afatfsOperationStatus_e afatfs_fileGetNextCluster(afatfsFilePtr_t file, u (void) file; #else if ((file->mode & AFATFS_FILE_MODE_CONTIGUOUS) != 0) { - uint32_t freeFileStart = afatfs_firstCluster(&afatfs.freeFile); + uint32_t freeFileStart = afatfs.freeFile.firstCluster; afatfs_assert(currentCluster + 1 <= freeFileStart); @@ -1289,18 +1352,24 @@ static afatfsOperationStatus_e afatfs_FATFillWithPattern(afatfsFATPattern_e patt /** * Write the directory entry for the file into its `directoryEntryPos` position in its containing directory. * + * mode: + * AFATFS_SAVE_DIRECTORY_NORMAL - Store the file's physical size, not the logical size, in the directory entry + * AFATFS_SAVE_DIRECTORY_FOR_CLOSE - We're done extending the file so we can write the logical size now. + * AFATFS_SAVE_DIRECTORY_DELETED - Mark the directory entry as deleted + * * Returns: * AFATFS_OPERATION_SUCCESS - The directory entry has been stored into the directory sector in cache. * AFATFS_OPERATION_IN_PROGRESS - Cache is too busy, retry later * AFATFS_OPERATION_FAILURE - If the filesystem enters the fatal state */ -static afatfsOperationStatus_e afatfs_saveDirectoryEntry(afatfsFilePtr_t file) +static afatfsOperationStatus_e afatfs_saveDirectoryEntry(afatfsFilePtr_t file, afatfsSaveDirectoryEntryMode_e mode) { uint8_t *sector; afatfsOperationStatus_e result; - if (file->directoryEntryPos.sectorNumberPhysical == 0) + if (file->directoryEntryPos.sectorNumberPhysical == 0) { return AFATFS_OPERATION_SUCCESS; // Root directories don't have a directory entry + } result = afatfs_cacheSector(file->directoryEntryPos.sectorNumberPhysical, §or, AFATFS_CACHE_READ | AFATFS_CACHE_WRITE); @@ -1311,11 +1380,32 @@ static afatfsOperationStatus_e afatfs_saveDirectoryEntry(afatfsFilePtr_t file) if (result == AFATFS_OPERATION_SUCCESS) { // (sub)directories don't store a filesize in their directory entry: if (file->type == AFATFS_FILE_TYPE_DIRECTORY) { - file->directoryEntry.fileSize = 0; + file->logicalSize = 0; } if (afatfs_assert(file->directoryEntryPos.entryIndex >= 0)) { - memcpy(sector + file->directoryEntryPos.entryIndex * FAT_DIRECTORY_ENTRY_SIZE, &file->directoryEntry, FAT_DIRECTORY_ENTRY_SIZE); + fatDirectoryEntry_t *entry = (fatDirectoryEntry_t *) sector + file->directoryEntryPos.entryIndex; + + switch (mode) { + case AFATFS_SAVE_DIRECTORY_NORMAL: + /* We exaggerate the length of the written file so that if power is lost, the end of the file will + * still be readable (though the very tail of the file will be uninitialized data). + * + * This way we can avoid updating the directory entry too many times during fwrites() on the file. + */ + entry->fileSize = file->physicalSize; + break; + case AFATFS_SAVE_DIRECTORY_DELETED: + entry->filename[0] = FAT_DELETED_FILE_MARKER; + //Fall through + + case AFATFS_SAVE_DIRECTORY_FOR_CLOSE: + // We write the true length of the file on close. + entry->fileSize = file->logicalSize; + } + + entry->firstClusterHigh = file->firstCluster >> 16; + entry->firstClusterLow = file->firstCluster & 0xFFFF; } else { return AFATFS_OPERATION_FAILURE; } @@ -1359,12 +1449,11 @@ static afatfsOperationStatus_e afatfs_appendRegularFreeClusterContinue(afatfsFil // Make the cluster available for us to write in file->cursorCluster = opState->searchCluster; + file->physicalSize += afatfs_clusterSize(); if (opState->previousCluster == 0) { - // This is the new first cluster in the file so we also need to update the directory entry - file->directoryEntry.firstClusterHigh = opState->searchCluster >> 16; - file->directoryEntry.firstClusterLow = opState->searchCluster & 0xFFFF; - + // This is the new first cluster in the file + file->firstCluster = opState->searchCluster; } opState->phase = AFATFS_APPEND_FREE_CLUSTER_PHASE_UPDATE_FAT1; @@ -1385,18 +1474,7 @@ static afatfsOperationStatus_e afatfs_appendRegularFreeClusterContinue(afatfsFil status = afatfs_FATSetNextCluster(opState->searchCluster, 0xFFFFFFFF); if (status == AFATFS_OPERATION_SUCCESS) { - // Is the new cluster the first cluster in the file? - if (opState->previousCluster == 0) { - opState->phase = AFATFS_APPEND_FREE_CLUSTER_PHASE_UPDATE_FILE_DIRECTORY; - } else { - opState->phase = AFATFS_APPEND_FREE_CLUSTER_PHASE_UPDATE_FAT2; - } - goto doMore; - } - break; - case AFATFS_APPEND_FREE_CLUSTER_PHASE_UPDATE_FILE_DIRECTORY: - if (afatfs_saveDirectoryEntry(file) == AFATFS_OPERATION_SUCCESS) { - opState->phase = AFATFS_APPEND_FREE_CLUSTER_PHASE_COMPLETE; + opState->phase = AFATFS_APPEND_FREE_CLUSTER_PHASE_UPDATE_FAT2; goto doMore; } break; @@ -1405,6 +1483,12 @@ static afatfsOperationStatus_e afatfs_appendRegularFreeClusterContinue(afatfsFil status = afatfs_FATSetNextCluster(opState->previousCluster, opState->searchCluster); if (status == AFATFS_OPERATION_SUCCESS) { + opState->phase = AFATFS_APPEND_FREE_CLUSTER_PHASE_UPDATE_FILE_DIRECTORY; + goto doMore; + } + break; + case AFATFS_APPEND_FREE_CLUSTER_PHASE_UPDATE_FILE_DIRECTORY: + if (afatfs_saveDirectoryEntry(file, AFATFS_SAVE_DIRECTORY_NORMAL) == AFATFS_OPERATION_SUCCESS) { opState->phase = AFATFS_APPEND_FREE_CLUSTER_PHASE_COMPLETE; goto doMore; } @@ -1469,7 +1553,8 @@ static afatfsOperationStatus_e afatfs_appendRegularFreeCluster(afatfsFilePtr_t f /** * Size of a AFATFS supercluster in bytes */ -static uint32_t afatfs_superClusterSize() +ONLY_EXPOSE_FOR_TESTING +uint32_t afatfs_superClusterSize() { return afatfs_fatEntriesPerSector() * afatfs_clusterSize(); } @@ -1493,7 +1578,7 @@ static afatfsOperationStatus_e afatfs_appendSuperclusterContinue(afatfsFile_t *f switch (opState->phase) { case AFATFS_APPEND_SUPERCLUSTER_PHASE_INIT: // Our file steals the first cluster of the freefile - freeFileStartCluster = afatfs_firstCluster(&afatfs.freeFile); + freeFileStartCluster = afatfs.freeFile.firstCluster; // The new supercluster needs to have its clusters chained contiguously and marked with a terminator at the end opState->fatRewriteStartCluster = freeFileStartCluster; @@ -1501,8 +1586,7 @@ static afatfsOperationStatus_e afatfs_appendSuperclusterContinue(afatfsFile_t *f if (opState->previousCluster == 0) { // This is the new first cluster in the file so we need to update the directory entry - file->directoryEntry.firstClusterHigh = afatfs.freeFile.directoryEntry.firstClusterHigh; - file->directoryEntry.firstClusterLow = afatfs.freeFile.directoryEntry.firstClusterLow; + file->firstCluster = afatfs.freeFile.firstCluster; } else { /* * We also need to update the FAT of the supercluster that used to end the file so that it no longer @@ -1512,7 +1596,8 @@ static afatfsOperationStatus_e afatfs_appendSuperclusterContinue(afatfsFile_t *f } // Remove the first supercluster from the freefile - afatfs.freeFile.directoryEntry.fileSize -= afatfs_superClusterSize(); + afatfs.freeFile.logicalSize -= afatfs_superClusterSize(); + afatfs.freeFile.physicalSize -= afatfs_superClusterSize(); uint32_t newFreeFileStartCluster; @@ -1524,35 +1609,34 @@ static afatfsOperationStatus_e afatfs_appendSuperclusterContinue(afatfsFile_t *f */ newFreeFileStartCluster = freeFileStartCluster + afatfs_fatEntriesPerSector(); - afatfs.freeFile.directoryEntry.firstClusterHigh = newFreeFileStartCluster >> 16; - afatfs.freeFile.directoryEntry.firstClusterLow = newFreeFileStartCluster & 0xFFFF; + afatfs.freeFile.firstCluster = newFreeFileStartCluster; - opState->phase = AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FAT; + // Update the file's allocated size + file->physicalSize += afatfs_superClusterSize(); + + opState->phase = AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FREEFILE_DIRECTORY; goto doMore; break; + case AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FREEFILE_DIRECTORY: + // First update the freefile's directory entry to remove the first supercluster so we don't risk cross-linking the file + status = afatfs_saveDirectoryEntry(&afatfs.freeFile, AFATFS_SAVE_DIRECTORY_NORMAL); + + if (status == AFATFS_OPERATION_SUCCESS) { + opState->phase = AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FAT; + goto doMore; + } + break; case AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FAT: status = afatfs_FATFillWithPattern(AFATFS_FAT_PATTERN_TERMINATED_CHAIN, &opState->fatRewriteStartCluster, opState->fatRewriteEndCluster); if (status == AFATFS_OPERATION_SUCCESS) { - opState->phase = AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FREEFILE_DIRECTORY; + opState->phase = AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FILE_DIRECTORY; goto doMore; } break; - case AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FREEFILE_DIRECTORY: - status = afatfs_saveDirectoryEntry(&afatfs.freeFile); - - if (status == AFATFS_OPERATION_SUCCESS) { - if (opState->previousCluster == 0) { - // Need to write the new first-cluster to the file's directory entry - opState->phase = AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FILE_DIRECTORY; - goto doMore; - } else { - return AFATFS_OPERATION_SUCCESS; - } - } - break; case AFATFS_APPEND_SUPERCLUSTER_PHASE_UPDATE_FILE_DIRECTORY: - status = afatfs_saveDirectoryEntry(file); + // Update the fileSize/firstCluster in the directory entry for the file + status = afatfs_saveDirectoryEntry(file, AFATFS_SAVE_DIRECTORY_NORMAL); break; } @@ -1578,7 +1662,7 @@ static afatfsOperationStatus_e afatfs_appendSupercluster(afatfsFilePtr_t file, u if (file->operation.operation == AFATFS_FILE_OPERATION_APPEND_SUPERCLUSTER) return AFATFS_OPERATION_IN_PROGRESS; - if (afatfs.freeFile.directoryEntry.fileSize < superClusterSize) { + if (afatfs.freeFile.logicalSize < superClusterSize) { afatfs.filesystemFull = true; } @@ -1596,7 +1680,7 @@ static afatfsOperationStatus_e afatfs_appendSupercluster(afatfsFilePtr_t file, u * We can go ahead and write to that space before the FAT and directory are updated by the * queued operation: */ - file->cursorCluster = afatfs_firstCluster(&afatfs.freeFile); + file->cursorCluster = afatfs.freeFile.firstCluster; status = afatfs_appendSuperclusterContinue(file); @@ -1727,7 +1811,7 @@ static uint8_t* afatfs_fileLockCursorSectorForWrite(afatfsFilePtr_t file) */ if ( cursorOffsetInSector > 0 - || (file->cursorOffset & ~(AFATFS_SECTOR_SIZE - 1)) + AFATFS_SECTOR_SIZE < file->directoryEntry.fileSize + || (file->cursorOffset & ~(AFATFS_SECTOR_SIZE - 1)) + AFATFS_SECTOR_SIZE < file->logicalSize ) { cacheFlags |= AFATFS_CACHE_READ; } @@ -1859,7 +1943,7 @@ static bool afatfs_fseekInternalContinue(afatfsFile_t *file) file->cursorOffset += opState->seekOffset; } - file->directoryEntry.fileSize = MAX(file->directoryEntry.fileSize, file->cursorOffset); + afatfs_fileUpdateFilesize(file); // TODO do we need this? file->operation.operation = AFATFS_FILE_OPERATION_NONE; @@ -1873,6 +1957,8 @@ static bool afatfs_fseekInternalContinue(afatfsFile_t *file) /** * Seek the file pointer forwards by offset bytes. Calls the callback when the seek is complete. * + * Will happily seek beyond the logical end of the file. + * * Returns: * AFATFS_OPERATION_SUCCESS - The seek was completed immediately * AFATFS_OPERATION_IN_PROGRESS - The seek was queued and will complete later @@ -1911,17 +1997,21 @@ static afatfsOperationStatus_e afatfs_fseekInternal(afatfsFilePtr_t file, uint32 * * Returns: * AFATFS_OPERATION_SUCCESS - The seek was completed immediately - * AFATFS_OPERATION_IN_PROGRESS - The seek was queued and will complete later + * AFATFS_OPERATION_IN_PROGRESS - The seek was queued and will complete later. Feel free to attempt read/write + * operations on the file, they'll fail until the seek is complete. * AFATFS_OPERATION_FAILURE - The seek could not be queued because the file was busy with another operation, * try again later. */ afatfsOperationStatus_e afatfs_fseek(afatfsFilePtr_t file, int32_t offset, afatfsSeek_e whence) { + // We need an up-to-date logical filesize so we can clamp seeks to the EOF + afatfs_fileUpdateFilesize(file); + switch (whence) { case AFATFS_SEEK_CUR: if (offset >= 0) { // Only forwards seeks are supported by this routine: - return afatfs_fseekInternal(file, offset, NULL); + return afatfs_fseekInternal(file, MIN(file->cursorOffset + offset, file->logicalSize), NULL); } // Convert a backwards relative seek into a SEEK_SET. TODO considerable room for improvement if within the same cluster @@ -1929,8 +2019,13 @@ afatfsOperationStatus_e afatfs_fseek(afatfsFilePtr_t file, int32_t offset, afatf break; case AFATFS_SEEK_END: + // Are we already at the right position? + if (file->logicalSize + offset == file->cursorOffset) { + return AFATFS_OPERATION_SUCCESS; + } + // Convert into a SEEK_SET - offset += file->directoryEntry.fileSize; + offset += file->logicalSize; break; case AFATFS_SEEK_SET: @@ -1942,11 +2037,11 @@ afatfsOperationStatus_e afatfs_fseek(afatfsFilePtr_t file, int32_t offset, afatf afatfs_fileUnlockCacheSector(file); file->cursorPreviousCluster = 0; - file->cursorCluster = afatfs_firstCluster(file); + file->cursorCluster = file->firstCluster; file->cursorOffset = 0; // Then seek forwards by the offset - return afatfs_fseekInternal(file, offset, NULL); + return afatfs_fseekInternal(file, MIN((uint32_t) offset, file->logicalSize), NULL); } /** @@ -2065,8 +2160,8 @@ static afatfsOperationStatus_e afatfs_extendSubdirectoryContinue(afatfsFile_t *d memset(dirEntries[0].filename, ' ', sizeof(dirEntries[0].filename)); dirEntries[0].filename[0] = '.'; - dirEntries[0].firstClusterHigh = directory->directoryEntry.firstClusterHigh; - dirEntries[0].firstClusterLow = directory->directoryEntry.firstClusterLow; + dirEntries[0].firstClusterHigh = directory->firstCluster >> 16; + dirEntries[0].firstClusterLow = directory->firstCluster & 0xFFFF; dirEntries[0].attrib = FAT_FILE_ATTRIBUTE_DIRECTORY; memset(dirEntries[1].filename, ' ', sizeof(dirEntries[1].filename)); @@ -2140,7 +2235,7 @@ static afatfsOperationStatus_e afatfs_extendSubdirectory(afatfsFile_t *directory directory->operation.operation = AFATFS_FILE_OPERATION_EXTEND_SUBDIRECTORY; opState->phase = AFATFS_EXTEND_SUBDIRECTORY_PHASE_INITIAL; - opState->parentDirectoryCluster = parentDirectory ? afatfs_firstCluster(parentDirectory) : 0; + opState->parentDirectoryCluster = parentDirectory ? parentDirectory->firstCluster : 0; opState->callback = callback; afatfs_appendRegularFreeClusterInitOperationState(&opState->appendFreeCluster, directory->cursorPreviousCluster); @@ -2218,17 +2313,17 @@ static afatfsFilePtr_t afatfs_allocateFileHandle() * was a truncate), then the truncate operation's callback is called. This allows the truncation to be called as a * sub-operation without it clearing the parent file operation. */ -static afatfsOperationStatus_e afatfs_ftruncateContinue(afatfsFilePtr_t file) +static afatfsOperationStatus_e afatfs_ftruncateContinue(afatfsFilePtr_t file, bool markDeleted) { afatfsTruncateFile_t *opState = &file->operation.state.truncateFile; afatfsOperationStatus_e status; - uint32_t oldFreeFileStart; + uint32_t oldFreeFileStart, freeFileGrow; doMore: switch (opState->phase) { case AFATFS_TRUNCATE_FILE_UPDATE_DIRECTORY: - status = afatfs_saveDirectoryEntry(file); + status = afatfs_saveDirectoryEntry(file, markDeleted ? AFATFS_SAVE_DIRECTORY_DELETED : AFATFS_SAVE_DIRECTORY_NORMAL); if (status == AFATFS_OPERATION_SUCCESS) { if (opState->endCluster) { @@ -2250,13 +2345,16 @@ static afatfsOperationStatus_e afatfs_ftruncateContinue(afatfsFilePtr_t file) break; case AFATFS_TRUNCATE_FILE_PREPEND_TO_FREEFILE: // Note, it's okay to run this code several times: - oldFreeFileStart = afatfs_firstCluster(&afatfs.freeFile); + oldFreeFileStart = afatfs.freeFile.firstCluster; - afatfs.freeFile.directoryEntry.firstClusterHigh = opState->startCluster >> 16; - afatfs.freeFile.directoryEntry.firstClusterLow = opState->startCluster & 0xFFFF; - afatfs.freeFile.directoryEntry.fileSize += (oldFreeFileStart - opState->startCluster) * afatfs_clusterSize(); + afatfs.freeFile.firstCluster = opState->startCluster; - status = afatfs_saveDirectoryEntry(&afatfs.freeFile); + freeFileGrow = (oldFreeFileStart - opState->startCluster) * afatfs_clusterSize(); + + afatfs.freeFile.logicalSize += freeFileGrow; + afatfs.freeFile.physicalSize += freeFileGrow; + + status = afatfs_saveDirectoryEntry(&afatfs.freeFile, AFATFS_SAVE_DIRECTORY_NORMAL); if (status == AFATFS_OPERATION_SUCCESS) { opState->phase = AFATFS_TRUNCATE_FILE_SUCCESS; goto doMore; @@ -2308,7 +2406,7 @@ static afatfsOperationStatus_e afatfs_ftruncateContinue(afatfsFilePtr_t file) } /** - * Truncate the file to zero bytes in length. + * Queue an operation to truncate the file to zero bytes in length. * * Returns true if the operation was successfully queued or false if the file is busy (try again later). * @@ -2326,27 +2424,38 @@ bool afatfs_ftruncate(afatfsFilePtr_t file, afatfsFileCallback_t callback) opState = &file->operation.state.truncateFile; opState->callback = callback; opState->phase = AFATFS_TRUNCATE_FILE_INITIAL; - opState->startCluster = afatfs_firstCluster(file); + opState->startCluster = file->firstCluster; opState->currentCluster = opState->startCluster; if ((file->mode & AFATFS_FILE_MODE_CONTIGUOUS) != 0) { // The file is contiguous and ends where the freefile begins - opState->endCluster = afatfs_firstCluster(&afatfs.freeFile); + opState->endCluster = afatfs.freeFile.firstCluster; } else { // The range of clusters to delete is not contiguous, so follow it as a linked-list instead opState->endCluster = 0; } // We'll drop the cluster chain from the directory entry immediately - file->directoryEntry.firstClusterHigh = 0; - file->directoryEntry.firstClusterLow = 0; - file->directoryEntry.fileSize = 0; + file->firstCluster = 0; + file->logicalSize = 0; + file->physicalSize = 0; afatfs_fseek(file, 0, AFATFS_SEEK_SET); return true; } +/** + * Load details from the given FAT directory entry into the file. + */ +static void afatfs_fileLoadDirectoryEntry(afatfsFile_t *file, fatDirectoryEntry_t *entry) +{ + file->firstCluster = (uint32_t) (entry->firstClusterHigh << 16) | entry->firstClusterLow; + file->logicalSize = entry->fileSize; + file->physicalSize = roundUpTo(entry->fileSize, afatfs_clusterSize()); + file->attrib = entry->attrib; +} + static void afatfs_createFileContinue(afatfsFile_t *file) { afatfsCreateFile_t *opState = &file->operation.state.createFile; @@ -2383,9 +2492,9 @@ static void afatfs_createFileContinue(afatfsFile_t *file) opState->phase = AFATFS_CREATEFILE_PHASE_FAILURE; goto doMore; } - } else if (strncmp(entry->filename, (char*) file->directoryEntry.filename, FAT_FILENAME_LENGTH) == 0) { + } else if (strncmp(entry->filename, (char*) opState->filename, FAT_FILENAME_LENGTH) == 0) { // We found a file with this name! - memcpy(&file->directoryEntry, entry, sizeof(file->directoryEntry)); + afatfs_fileLoadDirectoryEntry(file, entry); afatfs_findLast(&afatfs.currentDirectory); @@ -2407,10 +2516,17 @@ static void afatfs_createFileContinue(afatfsFile_t *file) status = afatfs_allocateDirectoryEntry(&afatfs.currentDirectory, &entry, &file->directoryEntryPos); if (status == AFATFS_OPERATION_SUCCESS) { - memcpy(entry, &file->directoryEntry, sizeof(file->directoryEntry)); + memset(entry, 0, sizeof(*entry)); + + memcpy(entry->filename, opState->filename, FAT_FILENAME_LENGTH); + entry->attrib = file->attrib; + entry->creationDate = AFATFS_DEFAULT_FILE_DATE; + entry->creationTime = AFATFS_DEFAULT_FILE_TIME; + entry->lastWriteDate = AFATFS_DEFAULT_FILE_DATE; + entry->lastWriteTime = AFATFS_DEFAULT_FILE_TIME; #ifdef AFATFS_DEBUG_VERBOSE - fprintf(stderr, "Adding directory entry for %.*s to sector %u\n", FAT_FILENAME_LENGTH, entry->filename, file->directoryEntryPos.sectorNumberPhysical); + fprintf(stderr, "Adding directory entry for %.*s to sector %u\n", FAT_FILENAME_LENGTH, opState->filename, file->directoryEntryPos.sectorNumberPhysical); #endif opState->phase = AFATFS_CREATEFILE_PHASE_SUCCESS; @@ -2468,7 +2584,7 @@ static void afatfs_createFileContinue(afatfsFile_t *file) if ((file->mode & AFATFS_FILE_MODE_APPEND) != 0) { // This replaces our open file operation file->operation.operation = AFATFS_FILE_OPERATION_NONE; - afatfs_fseekInternal(file, file->directoryEntry.fileSize, opState->callback); + afatfs_fseekInternal(file, file->logicalSize, opState->callback); break; } @@ -2506,7 +2622,7 @@ static void afatfs_funlinkContinue(afatfsFilePtr_t file) afatfsUnlinkFile_t *opState = &file->operation.state.unlinkFile; afatfsOperationStatus_e status; - status = afatfs_ftruncateContinue(file); + status = afatfs_ftruncateContinue(file, true); if (status == AFATFS_OPERATION_SUCCESS) { // Once the truncation is completed, we can close the file handle @@ -2538,11 +2654,6 @@ bool afatfs_funlink(afatfsFilePtr_t file, afatfsCallback_t callback) if (!afatfs_ftruncate(file, NULL)) return false; - /* The truncate operation will flush the directory entry for us, so go ahead and mark that as deleted in memory - * for it. - */ - file->directoryEntry.filename[0] = FAT_DELETED_FILE_MARKER; - /* * The unlink operation has its own private callback field so that the truncate suboperation doesn't end up * calling back early when it completes: @@ -2567,19 +2678,24 @@ bool afatfs_funlink(afatfsFilePtr_t file, afatfsCallback_t callback) static afatfsFilePtr_t afatfs_createFile(afatfsFilePtr_t file, const char *name, uint8_t attrib, uint8_t fileMode, afatfsFileCallback_t callback) { - afatfsCreateFile_t *opState; + afatfsCreateFile_t *opState = &file->operation.state.createFile; afatfs_initFileHandle(file); + // Queue the operation to finish the file creation + file->operation.operation = AFATFS_FILE_OPERATION_CREATE_FILE; + file->mode = fileMode; if (strcmp(name, ".") == 0) { - memcpy(&file->directoryEntry, &afatfs.currentDirectory.directoryEntry, sizeof(afatfs.currentDirectory.directoryEntry)); - + file->firstCluster = afatfs.currentDirectory.firstCluster; + file->physicalSize = afatfs.currentDirectory.physicalSize; + file->logicalSize = afatfs.currentDirectory.logicalSize; + file->attrib = afatfs.currentDirectory.attrib; file->type = afatfs.currentDirectory.type; } else { - fat_convertFilenameToFATStyle(name, (uint8_t*)file->directoryEntry.filename); - file->directoryEntry.attrib = attrib; + fat_convertFilenameToFATStyle(name, opState->filename); + file->attrib = attrib; if ((attrib & FAT_FILE_ATTRIBUTE_DIRECTORY) != 0) { file->type = AFATFS_FILE_TYPE_DIRECTORY; @@ -2588,10 +2704,6 @@ static afatfsFilePtr_t afatfs_createFile(afatfsFilePtr_t file, const char *name, } } - // Queue the operation to finish the file creation - file->operation.operation = AFATFS_FILE_OPERATION_CREATE_FILE; - - opState = &file->operation.state.createFile; opState->callback = callback; if (strcmp(name, ".") == 0) { @@ -2620,7 +2732,7 @@ static void afatfs_fcloseContinue(afatfsFilePtr_t file) */ if (file->type != AFATFS_FILE_TYPE_DIRECTORY && file->type != AFATFS_FILE_TYPE_FAT16_ROOT_DIRECTORY && (file->mode & (AFATFS_FILE_MODE_APPEND | AFATFS_FILE_MODE_WRITE | AFATFS_FILE_MODE_CREATE)) != 0) { - if (afatfs_saveDirectoryEntry(file) != AFATFS_OPERATION_SUCCESS) { + if (afatfs_saveDirectoryEntry(file, AFATFS_SAVE_DIRECTORY_FOR_CLOSE) != AFATFS_OPERATION_SUCCESS) { return; } } @@ -2667,6 +2779,8 @@ bool afatfs_fclose(afatfsFilePtr_t file, afatfsCallback_t callback) } else if (afatfs_fileIsBusy(file)) { return false; } else { + afatfs_fileUpdateFilesize(file); + file->operation.operation = AFATFS_FILE_OPERATION_CLOSE; file->operation.state.closeFile.callback = callback; afatfs_fcloseContinue(file); @@ -2720,9 +2834,8 @@ bool afatfs_chdir(afatfsFilePtr_t directory) else afatfs.currentDirectory.type = AFATFS_FILE_TYPE_DIRECTORY; - afatfs.currentDirectory.directoryEntry.firstClusterHigh = afatfs.rootDirectoryCluster >> 16; - afatfs.currentDirectory.directoryEntry.firstClusterLow = afatfs.rootDirectoryCluster & 0xFFFF; - afatfs.currentDirectory.directoryEntry.attrib = FAT_FILE_ATTRIBUTE_DIRECTORY; + afatfs.currentDirectory.firstCluster = afatfs.rootDirectoryCluster; + afatfs.currentDirectory.attrib = FAT_FILE_ATTRIBUTE_DIRECTORY; // Root directories don't have a directory entry to represent themselves: afatfs.currentDirectory.directoryEntryPos.sectorNumberPhysical = 0; @@ -2808,12 +2921,12 @@ void afatfs_fputc(afatfsFilePtr_t file, uint8_t c) int cacheIndex = file->writeLockedCacheIndex; + /* If we've already locked the current sector in the cache, and we won't be completing the sector, we won't + * be caching/uncaching/seeking, so we can just run this simpler fast case. + */ if (cacheIndex != -1 && cursorOffsetInSector != AFATFS_SECTOR_SIZE - 1) { afatfs_cacheSectorGetMemory(cacheIndex)[cursorOffsetInSector] = c; file->cursorOffset++; - - // TODO we could probably just defer this to the next seek, file close, or slow-path fwrite() call? - file->directoryEntry.fileSize = MAX(file->directoryEntry.fileSize, file->cursorOffset); } else { // Slow path afatfs_fwrite(file, &c, sizeof(c)); @@ -2869,12 +2982,12 @@ uint32_t afatfs_fwrite(afatfsFilePtr_t file, const uint8_t *buffer, uint32_t len * * If the seek has to queue, when the seek completes, it'll update the fileSize for us to contain the cursor. */ - if (afatfs_fseek(file, bytesToWriteThisSector, AFATFS_SEEK_CUR) == AFATFS_OPERATION_IN_PROGRESS) { + if (afatfs_fseekInternal(file, bytesToWriteThisSector, NULL) == AFATFS_OPERATION_IN_PROGRESS) { break; } if ((file->mode & AFATFS_FILE_MODE_CONTIGUOUS) != 0) { - afatfs_assert(file->cursorCluster < afatfs_firstCluster(&afatfs.freeFile)); + afatfs_assert(file->cursorCluster < afatfs.freeFile.firstCluster); } len -= bytesToWriteThisSector; @@ -2882,8 +2995,6 @@ uint32_t afatfs_fwrite(afatfsFilePtr_t file, const uint8_t *buffer, uint32_t len cursorOffsetInSector = 0; } - file->directoryEntry.fileSize = MAX(file->directoryEntry.fileSize, file->cursorOffset); - return writtenBytes; } @@ -2910,7 +3021,15 @@ uint32_t afatfs_fread(afatfsFilePtr_t file, uint8_t *buffer, uint32_t len) return 0; } - len = MIN(file->directoryEntry.fileSize - file->cursorOffset, len); + /* + * If we've just previously fwritten() to extend the file, the logical filesize will be out of date and the cursor + * will appear to be beyond the end of the file (but actually it's precisely at the end of the file, because if + * we had seeked backwards to where we could read something with fseek(), we would have updated the filesize). + */ + if (file->cursorOffset >= file->logicalSize) + return 0; + + len = MIN(file->logicalSize - file->cursorOffset, len); uint32_t readBytes = 0; uint32_t cursorOffsetInSector = file->cursorOffset % AFATFS_SECTOR_SIZE; @@ -2936,7 +3055,7 @@ uint32_t afatfs_fread(afatfsFilePtr_t file, uint8_t *buffer, uint32_t len) * A seek operation should always be able to queue on the file since we have checked that the file wasn't busy * on entry (fseek will never return AFATFS_OPERATION_FAILURE). */ - if (afatfs_fseek(file, bytesToReadThisSector, AFATFS_SEEK_CUR) == AFATFS_OPERATION_IN_PROGRESS) { + if (afatfs_fseekInternal(file, bytesToReadThisSector, NULL) == AFATFS_OPERATION_IN_PROGRESS) { break; } @@ -2950,7 +3069,7 @@ uint32_t afatfs_fread(afatfsFilePtr_t file, uint8_t *buffer, uint32_t len) bool afatfs_feof(afatfsFilePtr_t file) { - return file->cursorOffset >= file->directoryEntry.fileSize; + return file->cursorOffset >= file->logicalSize; } /** @@ -2975,7 +3094,7 @@ static void afatfs_fileOperationContinue(afatfsFile_t *file) afatfs_funlinkContinue(file); break; case AFATFS_FILE_OPERATION_TRUNCATE: - afatfs_ftruncateContinue(file); + afatfs_ftruncateContinue(file, false); break; #ifdef AFATFS_USE_FREEFILE case AFATFS_FILE_OPERATION_APPEND_SUPERCLUSTER: @@ -3018,7 +3137,7 @@ static void afatfs_fileOperationsPoll() */ uint32_t afatfs_getContiguousFreeSpace() { - return afatfs.freeFile.directoryEntry.fileSize; + return afatfs.freeFile.logicalSize; } /** @@ -3117,7 +3236,7 @@ static void afatfs_freeFileCreated(afatfsFile_t *file) { if (file) { // Did the freefile already have allocated space? - if (file->directoryEntry.fileSize > 0) { + if (file->logicalSize > 0) { afatfs.filesystemState = AFATFS_FILESYSTEM_STATE_READY; } else { // Allocate clusters for the freefile @@ -3199,10 +3318,10 @@ static void afatfs_initContinue() afatfs.initState.freeSpaceFAT.startCluster = startCluster; afatfs.initState.freeSpaceFAT.endCluster = endCluster; - afatfs.freeFile.directoryEntry.firstClusterHigh = startCluster >> 16; - afatfs.freeFile.directoryEntry.firstClusterLow = startCluster & 0xFFFF; + afatfs.freeFile.firstCluster = startCluster; - afatfs.freeFile.directoryEntry.fileSize = afatfs.initState.freeSpaceSearch.bestGapLength * afatfs_clusterSize(); + afatfs.freeFile.logicalSize = afatfs.initState.freeSpaceSearch.bestGapLength * afatfs_clusterSize(); + afatfs.freeFile.physicalSize = afatfs.freeFile.logicalSize; // We can write the FAT table for the freefile now afatfs.initPhase = AFATFS_INITIALIZATION_FREEFILE_UPDATE_FAT; @@ -3224,7 +3343,7 @@ static void afatfs_initContinue() } break; case AFATFS_INITIALIZATION_FREEFILE_SAVE_DIR_ENTRY: - status = afatfs_saveDirectoryEntry(&afatfs.freeFile); + status = afatfs_saveDirectoryEntry(&afatfs.freeFile, AFATFS_SAVE_DIRECTORY_NORMAL); if (status == AFATFS_OPERATION_SUCCESS) { afatfs.filesystemState = AFATFS_FILESYSTEM_STATE_READY; @@ -3272,11 +3391,13 @@ void afatfs_init() /** * Shut down the filesystem, flushing all data to the disk. Keep calling until it returns true. + * + * dirty - Set to true to skip the flush operation and terminate immediately (buffered data will be lost!) */ -bool afatfs_destroy() +bool afatfs_destroy(bool dirty) { // Only attempt detailed cleanup if the filesystem is in reasonable looking state - if (afatfs.filesystemState == AFATFS_FILESYSTEM_STATE_READY) { + if (!dirty && afatfs.filesystemState == AFATFS_FILESYSTEM_STATE_READY) { int openFileCount = 0; for (int i = 0; i < AFATFS_MAX_OPEN_FILES; i++) { @@ -3325,7 +3446,6 @@ bool afatfs_destroy() memset(&afatfs, 0, sizeof(afatfs)); return true; - } /** diff --git a/src/main/io/asyncfatfs/asyncfatfs.h b/src/main/io/asyncfatfs/asyncfatfs.h index afe94a2bca..e8ed508bc1 100644 --- a/src/main/io/asyncfatfs/asyncfatfs.h +++ b/src/main/io/asyncfatfs/asyncfatfs.h @@ -1,3 +1,20 @@ +/* + * This file is part of Cleanflight. + * + * Cleanflight is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Cleanflight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Cleanflight. If not, see . + */ + #pragma once #include @@ -57,7 +74,7 @@ void afatfs_findLast(afatfsFilePtr_t directory); bool afatfs_flush(); void afatfs_init(); -bool afatfs_destroy(); +bool afatfs_destroy(bool dirty); void afatfs_poll(); uint32_t afatfs_getFreeBufferSpace(); diff --git a/src/main/io/asyncfatfs/fat_standard.c b/src/main/io/asyncfatfs/fat_standard.c index 329256f436..c91d136c3f 100644 --- a/src/main/io/asyncfatfs/fat_standard.c +++ b/src/main/io/asyncfatfs/fat_standard.c @@ -1,3 +1,20 @@ +/* + * This file is part of Cleanflight. + * + * Cleanflight is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Cleanflight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Cleanflight. If not, see . + */ + #include #include "fat_standard.h" diff --git a/src/main/io/asyncfatfs/fat_standard.h b/src/main/io/asyncfatfs/fat_standard.h index 9010f7131f..590bc43a55 100644 --- a/src/main/io/asyncfatfs/fat_standard.h +++ b/src/main/io/asyncfatfs/fat_standard.h @@ -1,3 +1,20 @@ +/* + * This file is part of Cleanflight. + * + * Cleanflight is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Cleanflight is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Cleanflight. If not, see . + */ + #pragma once #include @@ -30,6 +47,9 @@ #define FAT_FILENAME_LENGTH 11 #define FAT_DELETED_FILE_MARKER 0xE5 +#define FAT_MAKE_DATE(year, month, day) (day | (month << 5) | ((year - 1980) << 9)) +#define FAT_MAKE_TIME(hour, minute, second) ((second / 2) | (minute << 5) | (hour << 11)) + typedef enum { FAT_FILESYSTEM_TYPE_INVALID, FAT_FILESYSTEM_TYPE_FAT12,