From e5bc716256e400a00584a792ec97d3c7e1d00ed8 Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Sat, 15 Jan 2022 01:09:47 +0100 Subject: [PATCH 01/17] Show short names when no long filenames found --- Marlin/src/sd/cardreader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp index 25f9d7d802f9..8c1d08a4605d 100644 --- a/Marlin/src/sd/cardreader.cpp +++ b/Marlin/src/sd/cardreader.cpp @@ -328,7 +328,7 @@ void CardReader::printListing( if (includeLongNames) { SERIAL_CHAR(' '); if (prependLong) { SERIAL_ECHO(prependLong); SERIAL_CHAR('/'); } - SERIAL_ECHO(longFilename[0] ? longFilename : "???"); + SERIAL_ECHO(longFilename[0] ? longFilename : filename); } #endif SERIAL_EOL(); @@ -385,9 +385,9 @@ void CardReader::ls( diveDir.rewind(); selectByName(diveDir, segment); - // Print /LongNamePart to serial output + // Print /LongNamePart to serial output or the short name if not available SERIAL_CHAR('/'); - SERIAL_ECHO(longFilename[0] ? longFilename : "???"); + SERIAL_ECHO(longFilename[0] ? longFilename : filename); // If the filename was printed then that's it if (!flag.filenameIsDir) break; From d3849914960b9488416da25df81120cef8d1a69c Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Sat, 15 Jan 2022 01:11:47 +0100 Subject: [PATCH 02/17] Support for long filenames (R/W) --- Marlin/src/sd/SdBaseFile.cpp | 541 ++++++++++++++++++++++++++++++----- Marlin/src/sd/SdBaseFile.h | 15 +- 2 files changed, 484 insertions(+), 72 deletions(-) diff --git a/Marlin/src/sd/SdBaseFile.cpp b/Marlin/src/sd/SdBaseFile.cpp index b357495a3edb..e680d25d1c08 100644 --- a/Marlin/src/sd/SdBaseFile.cpp +++ b/Marlin/src/sd/SdBaseFile.cpp @@ -40,6 +40,10 @@ #include "SdBaseFile.h" #include "../MarlinCore.h" + +#define DEBUG_OUT 1 +#include "../core/debug_out.h" + SdBaseFile *SdBaseFile::cwd_ = 0; // Pointer to Current Working Directory // callback function for date/time @@ -89,6 +93,7 @@ bool SdBaseFile::addDirCluster() { } // cache a file's directory entry +// cache the current "dirBlock_" and return the entry at index "dirIndex_" // return pointer to cached entry or null for failure dir_t* SdBaseFile::cacheDirEntry(uint8_t action) { if (!vol_->cacheRawBlock(dirBlock_, action)) return nullptr; @@ -384,6 +389,20 @@ int8_t SdBaseFile::lsPrintNext(uint8_t flags, uint8_t indent) { return DIR_IS_FILE(&dir) ? 1 : 2; } +/** + * Calculate a checksum for an 8.3 filename + * + * \param name The 8.3 file name to calculate + * + * \return The checksum byte + */ +uint8_t lfn_checksum(const uint8_t *name) { + uint8_t sum = 0; + for (uint8_t i = 11; i; i--) + sum = ((sum & 1) << 7) + (sum >> 1) + *name++; + return sum; +} + // Format directory name field from a 8.3 name string bool SdBaseFile::make83Name(const char *str, uint8_t *name, const char **ptr) { uint8_t n = 7, // Max index until a dot is found @@ -426,6 +445,7 @@ bool SdBaseFile::mkdir(SdBaseFile *parent, const char *path, bool pFlag) { if (ENABLED(SDCARD_READONLY)) return false; uint8_t dname[11]; + uint8_t dlname[LONG_FILENAME_LENGTH]; SdBaseFile dir1, dir2; SdBaseFile *sub = &dir1; SdBaseFile *start = parent; @@ -440,27 +460,28 @@ bool SdBaseFile::mkdir(SdBaseFile *parent, const char *path, bool pFlag) { } } while (1) { - if (!make83Name(path, dname, &path)) return false; + //if (!make83Name(path, dname, &path)) return false; + if (!parsePath(path, dname, dlname, &path)) return false; while (*path == '/') path++; if (!*path) break; - if (!sub->open(parent, dname, O_READ)) { - if (!pFlag || !sub->mkdir(parent, dname)) + if (!sub->open(parent, dname, dlname, O_READ)) { + if (!pFlag || !sub->mkdir(parent, dname, dlname)) return false; } if (parent != start) parent->close(); parent = sub; sub = parent != &dir1 ? &dir1 : &dir2; } - return mkdir(parent, dname); + return mkdir(parent, dname, dlname); } -bool SdBaseFile::mkdir(SdBaseFile *parent, const uint8_t dname[11]) { +bool SdBaseFile::mkdir(SdBaseFile *parent, const uint8_t dname[11], const uint8_t dlname[LONG_FILENAME_LENGTH]) { if (ENABLED(SDCARD_READONLY)) return false; if (!parent->isDir()) return false; // create a normal file - if (!open(parent, dname, O_CREAT | O_EXCL | O_RDWR)) return false; + if (!open(parent, dname, dlname, O_CREAT | O_EXCL | O_RDWR)) return false; // convert file to directory flags_ = O_READ; @@ -575,6 +596,7 @@ bool SdBaseFile::open(const char *path, uint8_t oflag) { */ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) { uint8_t dname[11]; + uint8_t dlname[LONG_FILENAME_LENGTH]; SdBaseFile dir1, dir2; SdBaseFile *parent = dirFile, *sub = &dir1; @@ -589,92 +611,221 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) { } for (;;) { - if (!make83Name(path, dname, &path)) return false; + if (!parsePath(path, dname, dlname, &path)) return false; while (*path == '/') path++; if (!*path) break; - if (!sub->open(parent, dname, O_READ)) return false; + if (!sub->open(parent, dname, dlname, O_READ)) return false; if (parent != dirFile) parent->close(); parent = sub; sub = parent != &dir1 ? &dir1 : &dir2; } - return open(parent, dname, oflag); + return open(parent, dname, dlname, oflag); } -// open with filename in dname -bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], uint8_t oflag) { +// open with filename in dname and long filename in dlname +bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_t dlname[LONG_FILENAME_LENGTH], uint8_t oflag) { bool emptyFound = false, fileFound = false; - uint8_t index; + uint8_t index = 0; dir_t *p; - + // LFN - Long File Name support + bool useLFN = dlname[0] != 0, lfnFileFound = false; + vfat_t *pvFat; + uint8_t emptyCount = 0; + uint8_t emptyIndex = 0; + uint8_t reqEntriesNum = useLFN ? getLFNEntriesNum((char *) dlname) + 1 : 1; + uint8_t lfnNameLength = useLFN ? strlen((char *) dlname) : 0; + uint8_t lfnName[LONG_FILENAME_LENGTH]; + uint8_t lfnSequenceNumber = 0; + uint8_t lfnChecksum = 0; + + //DEBUG_ECHOLNPGM("*_* SdBaseFile::open 2 - name: ", (char *)dname,", dlname: ", (char *)dlname, ", useLFN: ", useLFN, ", EntriesRequired: ", reqEntriesNum); + + // Rewind this dir vol_ = dirFile->vol_; - dirFile->rewind(); - // search for file + // search for file + //DEBUG_ECHOLNPGM("*_* This idx: ", dirIndex_, ", Block: ", dirBlock_, ", Cluster: ", curCluster_, ", CurrPosition: ", curPosition_, ", Size: ", fileSize_); while (dirFile->curPosition_ < dirFile->fileSize_) { - index = 0xF & (dirFile->curPosition_ >> 5); + // index = 0xF & (dirFile->curPosition_ >> 5); + index = dirFile->curPosition_ >> 5; + //DEBUG_ECHOLNPGM("*_* DirFile - idx: ", index, ", idx cache: ", index & 0xF, ", Cluster: ", dirFile->curCluster_, ", CurrPosition: ", dirFile->curPosition_, ", Size: ", dirFile->fileSize_); p = dirFile->readDirCache(); if (!p) return false; + // Check empty status + // Is entry empty? if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) { - // remember first empty slot + // Count the contiguous available entries in which (eventually) fit the new dir entry, if it's a write operation if (!emptyFound) { - dirBlock_ = dirFile->vol_->cacheBlockNumber(); - dirIndex_ = index; - emptyFound = true; + if (emptyCount == 0) { + //DEBUG_ECHOLNPGM("*_* Mark start empty @ idx: ", index); + emptyIndex = index; + } + // Incr empty entries counter + emptyCount++; + //DEBUG_ECHOLNPGM("*_* Empty entry at idx: ", index); + // If found the required empty entries, mark it + if (emptyCount == reqEntriesNum) { + //DEBUG_ECHOLNPGM("*_* Mark Complete empty @ idx: ", index); + dirBlock_ = dirFile->vol_->cacheBlockNumber(); + dirIndex_ = index & 0xF; + emptyFound = true; + } } - // done if no entries follow + // Done if no entries follow if (p->name[0] == DIR_NAME_FREE) break; } - else if (!memcmp(dname, p->name, 11)) { - fileFound = true; - break; + // Entry not empty + else { + // Reset empty counter + if (!emptyFound) emptyCount = 0; + // Search for SFN or LFN? + if (!useLFN) { + // Check using SFN + // File found? + if (!memcmp(dname, p->name, 11)) { + fileFound = true; + //DEBUG_ECHOLNPGM("*_* SFN '", (char *) dname , "' found @ idx: ", index); + break; + } + } + else { + // Check using LFN + // LFN not found? continue search for LFN + if (!lfnFileFound) { + // Is this dir a LFN? + if (isDirLFN(p)) { + // Get VFat dir entry + pvFat = (vfat_t *) p; + //DEBUG_ECHOLNPGM("*_* SdBaseFile::open 2 - Use LFN, curr lfnName: ", (char *) lfnName, ", + name1: ", (char *) pvFat->name1, ", sequenceNumber: ", pvFat->sequenceNumber, ", checksum: ", pvFat->checksum); + // Get checksum from the last entry of the sequence + if (pvFat->sequenceNumber & 0x40) lfnChecksum = pvFat->checksum; + // Get LFN sequence number + lfnSequenceNumber = pvFat->sequenceNumber & 0x1F; + if WITHIN(lfnSequenceNumber, 1, reqEntriesNum) { + // Check checksum for all other entries with the starting checksum fetched before + if (lfnChecksum == pvFat->checksum) { + // Set chunk of LFN from VFAT entry into lfnName + getLFNName(pvFat, (char *)lfnName, lfnSequenceNumber); + // LFN found? + if (!strncasecmp((char *) dlname, (char *) lfnName, lfnNameLength)) { + //DEBUG_ECHOLNPGM("*_* LFN '", (char *) dlname , "' found @ idx: ", index); + lfnFileFound = true; + } + } + } + } + } + // Complete LFN found, check for related SFN + else { + // Check if only the SFN checksum match because the filename may be different due to different truncation methods + if (!isDirLFN(p) && (lfnChecksum == lfn_checksum(p->name))) { + //DEBUG_ECHOLNPGM("*_* LFN '", (char *) dlname , "' & SFN '", (char *) p->name , "' found @ idx: ", index); + fileFound = true; + break; + } + else { + //DEBUG_ECHOLNPGM("*_* SFN not valid for the LFN found"); + // SFN not valid for the LFN found, reset LFN FileFound + lfnFileFound = false; + } + } + } } } if (fileFound) { // don't open existing file if O_EXCL if (oflag & O_EXCL) return false; + index &= 0xF; } else { // don't create unless O_CREAT and O_WRITE if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) return false; - if (emptyFound) { - index = dirIndex_; - p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE); - if (!p) return false; - } - else { - if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false; - // add and zero cluster for dirFile - first cluster is in cache for write - if (!dirFile->addDirCluster()) return false; + // Use bookmark index if found empty entries + if (emptyFound) index = emptyIndex; - // use first entry in cluster - p = dirFile->vol_->cache()->dir; - index = 0; + //DEBUG_ECHOLNPGM("*_* Empty Found:", emptyFound, ", Empty Index: ", emptyIndex, ", Empty counter: ", emptyCount, ", Curr Index: ", index); + + // Make room for needed entries + while (emptyCount < reqEntriesNum) + { + //DEBUG_ECHOLNPGM("*_* Make room with readDirCache"); + p = dirFile->readDirCache(); + if (!p) break; + emptyCount++; + //DEBUG_ECHOLNPGM("*_* Current emptyCount: ", emptyCount); + } + while (emptyCount < reqEntriesNum) + { + //DEBUG_ECHOLNPGM("*_* Make room with addDirCluster"); + if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false; + // add and zero cluster for dirFile - first cluster is in cache for write + if (!dirFile->addDirCluster()) return false; + emptyCount += dirFile->vol_->blocksPerCluster() * 16; + //DEBUG_ECHOLNPGM("*_* Current emptyCount: ", emptyCount); } - // initialize as empty file - memset(p, 0, sizeof(*p)); - memcpy(p->name, dname, 11); - // set timestamps - if (dateTime_) { - // call user date/time function - dateTime_(&p->creationDate, &p->creationTime); + // Move to 1st entry to write + if (!dirFile->seekSet(32 * index)) { + //DEBUG_ECHOLNPGM("*_* seekSet Failed!"); + return false; } - else { - // use default date/time - p->creationDate = FAT_DEFAULT_DATE; - p->creationTime = FAT_DEFAULT_TIME; + + // Dir entries write loop: [LFN] + SFN(1) + LOOP_L_N(dirWriteIdx, reqEntriesNum) { + index = (dirFile->curPosition_ / 32) & 0xF; + //DEBUG_ECHOLNPGM("*_* readDirCache at cache index: ", index); + p = dirFile->readDirCache(); + //DEBUG_ECHOLNPGM("*_* ", p ? "OK" : "Failed", ", CurrCluster: ", dirFile->curCluster_, ", CurrPosition: ", dirFile->curPosition_); + // LFN or SFN Entry? + if (dirWriteIdx < reqEntriesNum - 1) { + // Write LFN Entries + //DEBUG_ECHOLNPGM("*_* Adding LFN entry"); + pvFat = (vfat_t *) p; + // initialize as empty file + memset(pvFat, 0, sizeof(*pvFat)); + lfnSequenceNumber = (reqEntriesNum - dirWriteIdx - 1) & 0x1F; + pvFat->attributes = DIR_ATT_LONG_NAME; + pvFat->checksum = lfn_checksum(dname); + // Set sequence number and mark as last LFN entry if it's the 1st loop + pvFat->sequenceNumber = lfnSequenceNumber | (dirWriteIdx == 0 ? 0x40 : 0); + // Set LFN name block + //DEBUG_ECHOLNPGM("setLFNName for '", (char *) dlname, "', Seq: ", lfnSequenceNumber, ", index: ", index, ", block: ", dirBlock_); + setLFNName(pvFat, (char *) dlname, lfnSequenceNumber); + } + else { + // Write SFN Entry + //DEBUG_ECHOLNPGM("*_* Adding SFN entry"); + // initialize as empty file + memset(p, 0, sizeof(*p)); + memcpy(p->name, dname, 11); + + // set timestamps + if (dateTime_) { + // call user date/time function + dateTime_(&p->creationDate, &p->creationTime); + } + else { + // use default date/time + p->creationDate = FAT_DEFAULT_DATE; + p->creationTime = FAT_DEFAULT_TIME; + } + p->lastAccessDate = p->creationDate; + p->lastWriteDate = p->creationDate; + p->lastWriteTime = p->creationTime; + } + + // write entry to SD + dirFile->vol_->cacheSetDirty(); + if (!dirFile->vol_->cacheFlush()) return false; + } - p->lastAccessDate = p->creationDate; - p->lastWriteDate = p->creationDate; - p->lastWriteTime = p->creationTime; - // write entry to SD - if (!dirFile->vol_->cacheFlush()) return false; } // open entry in cache + //DEBUG_ECHOLNPGM("*_* Opening idx: ", index); return openCachedEntry(index, oflag); } @@ -808,6 +959,217 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) { return false; } + +/** + * Check if dir is a long file name entry (LFN) + * + * \param[in] dir Parent of this directory will be opened. Must not be root. + * \return true if the dir is a long file name entry (LFN) + */ +bool SdBaseFile::isDirLFN(const dir_t* dir) { + if (DIR_IS_LONG_NAME(dir)) { + vfat_t *VFAT = (vfat_t*)dir; + // Sanity-check the VFAT entry. The first cluster is always set to zero. And the sequence number should be higher than 0 + if ((VFAT->firstClusterLow == 0) && WITHIN((VFAT->sequenceNumber & 0x1F), 1, MAX_VFAT_ENTRIES)) return true; + } + return false; +} + +/** + * Check if dirname string is a long file name (LFN) + * + * \param[in] dirname The string to check + * \return true if the dirname is a long file name (LFN) + * \return false if the dirname is a short file name 8.3 (SFN) + */ +bool SdBaseFile::isDirNameLFN(const char *dirname) { + uint8_t length = strlen(dirname); + uint8_t idx = length; + bool dotFound = false; + if (idx > 12) return true; // LFN due to filename length > 12 ("filename.ext") + // Check dot(s) position + while (idx) { + if (dirname[--idx] == '.') { + if (!dotFound) { + // Last dot (extension) is allowed only + // in position [1..8] from start or [0..3] from end for SFN else it's a LFN + // A filename starting with "." is a LFN (eg. ".file" ->in SFN-> "file~1 ") + // A filename ending with "." is a SFN (if length <= 9) (eg. "file." ->in SFN-> "file ") + if (idx > 8 || idx == 0 || (length - idx - 1) > 3) return true; // LFN due to dot extension position + dotFound = true; + } + else { + // Found another dot, is a LFN + return true; + } + } + } + // If no dots found, the filename must be of max 8 characters + if ((!dotFound) && length > 8) return true; // LFN due to max filename (without extension) length + return false; +} + + +/* + Parse path and return 8.3 format and LFN filenames (if the parsed path is a LFN) + The SFN is without dot ("FILENAMEEXT") + The LFN is complete ("Filename.ext") +*/ +bool SdBaseFile::parsePath(const char *path, uint8_t *name, uint8_t *lname, const char **ptrNextPath) { + //DEBUG_ECHOLNPGM("*_* SdBaseFile::parsePath - Path: ", path, ", Length: ", strlen(path)); + // Init randomizer for SFN generation + randomSeed(millis()); + // Parse the LFN + uint8_t ilfn = 0; + bool lastDotFound = false; + const char *pLastDot = 0; + const char *lfnpath = path; + uint8_t c; + + while (*lfnpath && *lfnpath != '/') { + if (ilfn == LONG_FILENAME_LENGTH - 1) return false; // Name too long + c = *lfnpath++; // Get char and advance + // Fail for illegal characters + PGM_P p = PSTR("|<>^+=?/[];:,*\"\\"); + while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; // Check reserved characters + if (c < 0x20 || c == 0x7F) return false; // Check non-printable characters + if (c == '.' && (lfnpath - 1) > path) { // Skip dot '.' check in 1st position + // Save last dot pointer (skip if starts with '.') + pLastDot = lfnpath - 1; + lastDotFound = true; + } + lname[ilfn++] = c; // Set LFN character + } + // Terminate LFN + lname[ilfn] = 0; + + //DEBUG_ECHOLNPGM("*_* SdBaseFile::parsePath - LFN parsed path: ", (char *) lname, ", Length: ", strlen((char *)lname), ", IsLFN:", isDirNameLFN((char *)lname)); + + // Parse/generate 8.3 SFN. Will take + // until 8 characters for the filename part + // until 3 characters for the extension part (if exists) + // Add 4 more characters if name part < 3 + // Add '~cnt' if it's a LFN + bool isLFN = isDirNameLFN((char *) lname); + + uint8_t n = isLFN ? 5 : 7, // Max index for each component of the file: + // starting with 7 or 5 (if LFN) + // switch to 10 for extension if the last dot is found + i = 11; + while (i) name[--i] = ' '; // Set whole FILENAMEEXT to spaces + while (*path && *path != '/') { + c = *path++; // Get char and advance + // Skip spaces and dots (if it's not the last dot) + if (c == ' ') continue; + if (c == '.' && (!lastDotFound || (lastDotFound && path < pLastDot))) continue; + // Fail for illegal characters + PGM_P p = PSTR("|<>^+=?/[];:,*\"\\"); + while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; // Check reserved characters + if (c < 0x21 || c == 0x7F) return false; // Check non-printable characters + // Is last dot? + if (c == '.') { + // Switch to extension part + n = 10; + i = 8; + } + // If in valid range add the character + else if (i <= n) // Check size for 8.3 format + name[i++] = c + (WITHIN(c, 'a', 'z') ? 'A' - 'a' : 0); // Uppercase required for 8.3 name + } + // If it's a LFN then the SFN always need: + // - A minimal of 3 characters (otherwise 4 chars are added) + // - The '~cnt' at the end + if (isLFN) { + // Get the 1st free character + uint8_t iFree = 0; + while (1) if (name[iFree++] == ' ' || iFree == 11) break; + iFree--; + // Check minimal length + if (iFree < 3) { + // Append 4 extra characters + name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(1,9) + 'A'; + name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(1,9) + 'A'; + } + // Append '~cnt' characters + if (iFree > 5) iFree = 5; // Force the append in the last 3 characters of name part + name[iFree++] = '~'; + name[iFree++] = random(1,9) + '0'; + name[iFree++] = random(1,9) + '0'; + } + +/* + const char *sfnpath = (char *) lname; + // uint8_t n = 7, // Max index until a dot is found + // i = 11; + // while (i) name[--i] = ' '; // Set whole FILENAME.EXT to spaces + while (*sfnpath && *sfnpath != '/') { // For each character, until nul or '/' + uint8_t c = *sfnpath++; // Get char and advance + if (c == '.') { // For a dot... + if (n == 10) return false; // Already moved the max index? fail! + n = 10; // Move the max index for full 8.3 name + i = 8; // Move up to the extension place + } + else { + // Fail for illegal characters + PGM_P p = PSTR("|<>^+=?/[];,*\"\\"); + while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; + if (c < 0x21 || c == 0x7F) return false; // Check non-printable characters + if (i <= n) // Check size for 8.3 format + name[i++] = c + (WITHIN(c, 'a', 'z') ? 'A' - 'a' : 0); // Uppercase required for 8.3 name + } + } +*/ + // Check if LFN is needed + if (!isLFN) lname[0] = 0; // Zero LFN + *ptrNextPath = path; // Set passed pointer to the end + //DEBUG_ECHOLNPGM("*_* SdBaseFile::parsePath, SFN: '", (char *) name, "', LFN: '", (char *) lname, "'"); + return name[0] != ' '; // Return true if any name was set +} + +/* + Get the LFN filename block from a dir. Get the block in lname at startOffset +*/ +void SdBaseFile::getLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) { + uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH; + LOOP_L_N(i, FILENAME_LENGTH) { + const uint16_t utf16_ch = (i >= 11) ? pFatDir->name3[i - 11] : (i >= 5) ? pFatDir->name2[i - 5] : pFatDir->name1[i]; + #if ENABLED(UTF_FILENAME_SUPPORT) + // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks + // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later. + uint16_t idx = (startOffset + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding + longFilename[idx] = utf16_ch & 0xFF; + longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF; + #else + // Replace all multibyte characters to '_' + lname[startOffset + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF); + #endif + } +} + +/* + Set the LFN filename block lname to a dir. Put the block based on sequence number +*/ +void SdBaseFile::setLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) { + uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH; + uint8_t nameLength = strlen(lname); + //DEBUG_ECHOLNPGM("*_* SdBaseFile::setLFNName - Starting from pos=", startOffset, " (seq ", sequenceNumber,") of ", lname); + LOOP_L_N(i, FILENAME_LENGTH) { + uint16_t ch = 0; + if ((startOffset + i) < nameLength) + ch = lname[startOffset + i]; + else if ((startOffset + i) > nameLength) + ch = 0xFFFF; + // Set char + if (i < 5) + pFatDir->name1[i] = ch; + else if (i < 11) + pFatDir->name2[i - 5] = ch; + else + pFatDir->name3[i - 11] = ch; + } +} + + #if 0 /** * Open a directory's parent directory. @@ -1049,20 +1411,6 @@ int16_t SdBaseFile::read(void *buf, uint16_t nbyte) { return nbyte; } -/** - * Calculate a checksum for an 8.3 filename - * - * \param name The 8.3 file name to calculate - * - * \return The checksum byte - */ -uint8_t lfn_checksum(const uint8_t *name) { - uint8_t sum = 0; - for (uint8_t i = 11; i; i--) - sum = ((sum & 1) << 7) + (sum >> 1) + *name++; - return sum; -} - /** * Read the next entry in a directory. * @@ -1110,14 +1458,18 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) { if (VFAT->firstClusterLow == 0) { const uint8_t seq = VFAT->sequenceNumber & 0x1F; if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) { - n = (seq - 1) * (FILENAME_LENGTH); - if (n == 0) { + //n = (seq - 1) * (FILENAME_LENGTH); + if (seq == 1) { checksum = VFAT->checksum; checksum_error = 0; } else if (checksum != VFAT->checksum) // orphan detected checksum_error = 1; + // Get chunk of LFN from VFAT entry + //getLFNName(VFAT, longFilename, n); + getLFNName(VFAT, longFilename, seq); + /* LOOP_L_N(i, FILENAME_LENGTH) { const uint16_t utf16_ch = (i >= 11) ? VFAT->name3[i - 11] : (i >= 5) ? VFAT->name2[i - 5] : VFAT->name1[i]; #if ENABLED(UTF_FILENAME_SUPPORT) @@ -1131,9 +1483,11 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) { longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF); #endif } + */ // If this VFAT entry is the last one, add a NUL terminator at the end of the string if (VFAT->sequenceNumber & 0x40) - longFilename[(n + FILENAME_LENGTH) * LONG_FILENAME_CHARSIZE] = '\0'; + longFilename[seq * FILENAME_LENGTH * LONG_FILENAME_CHARSIZE] = '\0'; + //longFilename[(n + FILENAME_LENGTH) * LONG_FILENAME_CHARSIZE] = '\0'; } } } @@ -1220,6 +1574,7 @@ dir_t* SdBaseFile::readDirCache() { bool SdBaseFile::remove() { if (ENABLED(SDCARD_READONLY)) return false; + //DEBUG_ECHOLNPGM("*_* SdBaseFile::remove - CurrPosition: ", curPosition_, ", dirIndex_: ", dirIndex_, ", dirBlock_: ", dirBlock_); // free any clusters - will fail if read-only or directory if (!truncate(0)) return false; @@ -1227,14 +1582,60 @@ bool SdBaseFile::remove() { dir_t *d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE); if (!d) return false; + // get SFN checksum before name rewrite (needed for LFN deletion) + uint8_t sfn_checksum = lfn_checksum(d->name); + // mark entry deleted d->name[0] = DIR_NAME_DELETED; // set this file closed type_ = FAT_FILE_TYPE_CLOSED; + flags_ = 0; // write entry to SD - return vol_->cacheFlush(); + if (!vol_->cacheFlush()) return false; + //DEBUG_ECHOLNPGM("*_* SdBaseFile::remove - SFN deleted @ Idx: ", dirIndex_); + + // Check if the entry has a LFN + //DEBUG_ECHOLNPGM("*_* Original - name:", (char *) d->name, ", checksum: ", sfn_checksum, ", dirIndex_: ", dirIndex_); + bool lastEntry = false; + // loop back to search for any LFN entries related to this file + LOOP_S_LE_N(sequenceNumber, 1, MAX_VFAT_ENTRIES) { + dirIndex_ = (dirIndex_ - 1) & 0xF; + if (dirBlock_ == 0) break; + if (dirIndex_ == 0xF) dirBlock_--; + //DEBUG_ECHOLNPGM("*_* Seeking to previous entry from dirIndex_: ", dirIndex_, ", dirBlock_: ", dirBlock_); + dir_t *dir = cacheDirEntry(SdVolume::CACHE_FOR_WRITE); + if (!dir) { + //DEBUG_ECHOLNPGM("*_* cacheDirEntry Failed"); + return false; + } + + // check for valid LFN: not deleted, not top dirs (".", ".."), must be a LFN + if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.' || !isDirLFN(dir)) break; + // check coherent LFN: checksum and sequenceNumber must match + //DEBUG_ECHOLNPGM("*_* Found LFN- name:", (char *) dir->name, ", Index: ", dirIndex_); + vfat_t* dirlfn = (vfat_t*) dir; + //DEBUG_ECHOLNPGM("*_* PRE Info - Index: ", dirIndex_, ", dirIndex_: ", dirIndex_, ", dirBlock_: ", dirBlock_,", sequenceNumber: ", dirlfn->sequenceNumber, ", checksum: ", dirlfn->checksum); + if (dirlfn->checksum != sfn_checksum || (dirlfn->sequenceNumber & 0x1F) != sequenceNumber) break; // orphan entry + //DEBUG_ECHOLNPGM("*_* DEL Info - Index: ", dirIndex_, ", dirIndex_: ", dirIndex_, ", dirBlock_: ", dirBlock_,", sequenceNumber: ", dirlfn->sequenceNumber, ", checksum: ", dirlfn->checksum); + // is last entry of LFN ? + lastEntry = (dirlfn->sequenceNumber & 0x40); + // mark as deleted + dirlfn->sequenceNumber = DIR_NAME_DELETED; + // Flush to SD + if (!vol_->cacheFlush()) return false; + // exit on last entry of LFN deleted + if (lastEntry) { + //DEBUG_ECHOLNPGM("*_* SdBaseFile::remove - LFN Last entry deleted @ Idx: ", dirIndex_); + break; + } + } + + // Restore current index + //if (!seekSet(32UL * dirIndex_)) return false; + //dirIndex_ += prevDirIndex; + return true; } diff --git a/Marlin/src/sd/SdBaseFile.h b/Marlin/src/sd/SdBaseFile.h index 342edefb7079..cc912d289645 100644 --- a/Marlin/src/sd/SdBaseFile.h +++ b/Marlin/src/sd/SdBaseFile.h @@ -377,8 +377,19 @@ class SdBaseFile { dir_t* cacheDirEntry(uint8_t action); int8_t lsPrintNext(uint8_t flags, uint8_t indent); static bool make83Name(const char *str, uint8_t *name, const char **ptr); - bool mkdir(SdBaseFile *parent, const uint8_t dname[11]); - bool open(SdBaseFile *dirFile, const uint8_t dname[11], uint8_t oflag); + bool mkdir(SdBaseFile *parent, const uint8_t dname[11], const uint8_t dlname[LONG_FILENAME_LENGTH]); + bool open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_t dlname[LONG_FILENAME_LENGTH], uint8_t oflag); bool openCachedEntry(uint8_t cacheIndex, uint8_t oflags); dir_t* readDirCache(); + + // LFN support + static bool isDirLFN(const dir_t* dir); + static bool isDirNameLFN(const char *dirname); + static bool parsePath(const char *str, uint8_t *name, uint8_t *lname, const char **ptr); + /* + Return the number of entries needed in the FAT for this LFN + */ + static inline uint8_t getLFNEntriesNum(const char *lname) { return (strlen(lname) + 12) / 13; } + static void getLFNName(vfat_t *vFatDir, char *lname, uint8_t startOffset); + static void setLFNName(vfat_t *vFatDir, char *lname, uint8_t lfnSequenceNumber); }; From 4764bd23ad9c98eefdd3343c8924849f4d393eff Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Sat, 15 Jan 2022 01:14:53 +0100 Subject: [PATCH 03/17] Support for long filenames in firmware upload --- buildroot/share/scripts/upload.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/buildroot/share/scripts/upload.py b/buildroot/share/scripts/upload.py index bfce4ea49dfd..610366ffb4f5 100644 --- a/buildroot/share/scripts/upload.py +++ b/buildroot/share/scripts/upload.py @@ -84,21 +84,26 @@ def _CheckSDCard(): #----------------# # File functions # #----------------# - def _GetFirmwareFiles(): + def _GetFirmwareFiles(UseLongFilenames): if Debug: print('Get firmware files...') - _Send('M20 F') + _Send(f"M20 F{'L' if UseLongFilenames else ''}") Responses = _Recv() if len(Responses) < 3 or not any('file list' in r for r in Responses): raise Exception('Error getting firmware files') if Debug: print('OK') return Responses - def _FilterFirmwareFiles(FirmwareList): + def _FilterFirmwareFiles(FirmwareList, UseLongFilenames): Firmwares = [] for FWFile in FirmwareList: - if not '/' in FWFile and '.BIN' in FWFile: - idx = FWFile.index('.BIN') - Firmwares.append(FWFile[:idx+4]) + # For long filenames take the 3rd column of the firmwares list + if UseLongFilenames: + Space = 0 + Space = FWFile.find(' ') + if Space >= 0: Space = FWFile.find(' ', Space + 1) + if Space >= 0: FWFile = FWFile[Space + 1:] + if not '/' in FWFile and '.BIN' in FWFile.upper(): + Firmwares.append(FWFile[:FWFile.upper().index('.BIN') + 4]) return Firmwares def _RemoveFirmwareFile(FirmwareFile): @@ -187,8 +192,8 @@ def _RemoveFirmwareFile(FirmwareFile): # Generate a new 8.3 random filename # This board remember the last firmware filename and doesn't allow to flash from that filename - upload_firmware_target_name = f"fw-{''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=5))}.BIN" - print(f"Board {marlin_motherboard}: Overriding firmware filename to '{upload_firmware_target_name}'") + #upload_firmware_target_name = f"fw-{''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=5))}.BIN" + #print(f"Board {marlin_motherboard}: Overriding firmware filename to '{upload_firmware_target_name}'") # Init serial port port = serial.Serial(upload_port, baudrate = upload_speed, write_timeout = 0, timeout = 0.1) @@ -198,13 +203,13 @@ def _RemoveFirmwareFile(FirmwareFile): _CheckSDCard() # Get firmware files - FirmwareFiles = _GetFirmwareFiles() + FirmwareFiles = _GetFirmwareFiles(marlin_custom_firmware_upload) if Debug: for FirmwareFile in FirmwareFiles: print(f'Found: {FirmwareFile}') # Get all 1st level firmware files (to remove) - OldFirmwareFiles = _FilterFirmwareFiles(FirmwareFiles[1:len(FirmwareFiles)-2]) # Skip header and footers of list + OldFirmwareFiles = _FilterFirmwareFiles(FirmwareFiles[1:len(FirmwareFiles)-2], marlin_custom_firmware_upload) # Skip header and footers of list if len(OldFirmwareFiles) == 0: print('No old firmware files to delete') else: From 2f570c801b31a4b6e70dbad1537add93b11c0d6e Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Sat, 15 Jan 2022 01:38:33 +0100 Subject: [PATCH 04/17] Remove debug --- Marlin/src/sd/SdBaseFile.cpp | 109 +++++------------------------------ 1 file changed, 13 insertions(+), 96 deletions(-) diff --git a/Marlin/src/sd/SdBaseFile.cpp b/Marlin/src/sd/SdBaseFile.cpp index e680d25d1c08..f1cdf6c4ab52 100644 --- a/Marlin/src/sd/SdBaseFile.cpp +++ b/Marlin/src/sd/SdBaseFile.cpp @@ -40,10 +40,6 @@ #include "SdBaseFile.h" #include "../MarlinCore.h" - -#define DEBUG_OUT 1 -#include "../core/debug_out.h" - SdBaseFile *SdBaseFile::cwd_ = 0; // Pointer to Current Working Directory // callback function for date/time @@ -638,18 +634,15 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_ uint8_t lfnSequenceNumber = 0; uint8_t lfnChecksum = 0; - //DEBUG_ECHOLNPGM("*_* SdBaseFile::open 2 - name: ", (char *)dname,", dlname: ", (char *)dlname, ", useLFN: ", useLFN, ", EntriesRequired: ", reqEntriesNum); - // Rewind this dir vol_ = dirFile->vol_; dirFile->rewind(); // search for file - //DEBUG_ECHOLNPGM("*_* This idx: ", dirIndex_, ", Block: ", dirBlock_, ", Cluster: ", curCluster_, ", CurrPosition: ", curPosition_, ", Size: ", fileSize_); while (dirFile->curPosition_ < dirFile->fileSize_) { - // index = 0xF & (dirFile->curPosition_ >> 5); + // Get absolute index position index = dirFile->curPosition_ >> 5; - //DEBUG_ECHOLNPGM("*_* DirFile - idx: ", index, ", idx cache: ", index & 0xF, ", Cluster: ", dirFile->curCluster_, ", CurrPosition: ", dirFile->curPosition_, ", Size: ", dirFile->fileSize_); + // Get next entry p = dirFile->readDirCache(); if (!p) return false; @@ -658,16 +651,11 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_ if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) { // Count the contiguous available entries in which (eventually) fit the new dir entry, if it's a write operation if (!emptyFound) { - if (emptyCount == 0) { - //DEBUG_ECHOLNPGM("*_* Mark start empty @ idx: ", index); - emptyIndex = index; - } + if (emptyCount == 0) emptyIndex = index; // Incr empty entries counter emptyCount++; - //DEBUG_ECHOLNPGM("*_* Empty entry at idx: ", index); // If found the required empty entries, mark it if (emptyCount == reqEntriesNum) { - //DEBUG_ECHOLNPGM("*_* Mark Complete empty @ idx: ", index); dirBlock_ = dirFile->vol_->cacheBlockNumber(); dirIndex_ = index & 0xF; emptyFound = true; @@ -676,29 +664,24 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_ // Done if no entries follow if (p->name[0] == DIR_NAME_FREE) break; } - // Entry not empty - else { + else { // Entry not empty // Reset empty counter if (!emptyFound) emptyCount = 0; // Search for SFN or LFN? if (!useLFN) { - // Check using SFN - // File found? + // Check using SFN: file found? if (!memcmp(dname, p->name, 11)) { fileFound = true; - //DEBUG_ECHOLNPGM("*_* SFN '", (char *) dname , "' found @ idx: ", index); break; } } else { - // Check using LFN - // LFN not found? continue search for LFN + // Check using LFN: LFN not found? continue search for LFN if (!lfnFileFound) { // Is this dir a LFN? if (isDirLFN(p)) { // Get VFat dir entry pvFat = (vfat_t *) p; - //DEBUG_ECHOLNPGM("*_* SdBaseFile::open 2 - Use LFN, curr lfnName: ", (char *) lfnName, ", + name1: ", (char *) pvFat->name1, ", sequenceNumber: ", pvFat->sequenceNumber, ", checksum: ", pvFat->checksum); // Get checksum from the last entry of the sequence if (pvFat->sequenceNumber & 0x40) lfnChecksum = pvFat->checksum; // Get LFN sequence number @@ -709,27 +692,18 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_ // Set chunk of LFN from VFAT entry into lfnName getLFNName(pvFat, (char *)lfnName, lfnSequenceNumber); // LFN found? - if (!strncasecmp((char *) dlname, (char *) lfnName, lfnNameLength)) { - //DEBUG_ECHOLNPGM("*_* LFN '", (char *) dlname , "' found @ idx: ", index); - lfnFileFound = true; - } + if (!strncasecmp((char *) dlname, (char *) lfnName, lfnNameLength)) lfnFileFound = true; } } } } - // Complete LFN found, check for related SFN - else { + else { // Complete LFN found, check for related SFN // Check if only the SFN checksum match because the filename may be different due to different truncation methods if (!isDirLFN(p) && (lfnChecksum == lfn_checksum(p->name))) { - //DEBUG_ECHOLNPGM("*_* LFN '", (char *) dlname , "' & SFN '", (char *) p->name , "' found @ idx: ", index); fileFound = true; break; } - else { - //DEBUG_ECHOLNPGM("*_* SFN not valid for the LFN found"); - // SFN not valid for the LFN found, reset LFN FileFound - lfnFileFound = false; - } + else lfnFileFound = false; // SFN not valid for the LFN found, reset LFN FileFound } } } @@ -746,43 +720,31 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_ // Use bookmark index if found empty entries if (emptyFound) index = emptyIndex; - //DEBUG_ECHOLNPGM("*_* Empty Found:", emptyFound, ", Empty Index: ", emptyIndex, ", Empty counter: ", emptyCount, ", Curr Index: ", index); - // Make room for needed entries while (emptyCount < reqEntriesNum) { - //DEBUG_ECHOLNPGM("*_* Make room with readDirCache"); p = dirFile->readDirCache(); if (!p) break; emptyCount++; - //DEBUG_ECHOLNPGM("*_* Current emptyCount: ", emptyCount); } while (emptyCount < reqEntriesNum) { - //DEBUG_ECHOLNPGM("*_* Make room with addDirCluster"); if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false; // add and zero cluster for dirFile - first cluster is in cache for write if (!dirFile->addDirCluster()) return false; emptyCount += dirFile->vol_->blocksPerCluster() * 16; - //DEBUG_ECHOLNPGM("*_* Current emptyCount: ", emptyCount); } // Move to 1st entry to write - if (!dirFile->seekSet(32 * index)) { - //DEBUG_ECHOLNPGM("*_* seekSet Failed!"); - return false; - } + if (!dirFile->seekSet(32 * index)) return false; // Dir entries write loop: [LFN] + SFN(1) LOOP_L_N(dirWriteIdx, reqEntriesNum) { index = (dirFile->curPosition_ / 32) & 0xF; - //DEBUG_ECHOLNPGM("*_* readDirCache at cache index: ", index); p = dirFile->readDirCache(); - //DEBUG_ECHOLNPGM("*_* ", p ? "OK" : "Failed", ", CurrCluster: ", dirFile->curCluster_, ", CurrPosition: ", dirFile->curPosition_); // LFN or SFN Entry? if (dirWriteIdx < reqEntriesNum - 1) { // Write LFN Entries - //DEBUG_ECHOLNPGM("*_* Adding LFN entry"); pvFat = (vfat_t *) p; // initialize as empty file memset(pvFat, 0, sizeof(*pvFat)); @@ -792,12 +754,10 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_ // Set sequence number and mark as last LFN entry if it's the 1st loop pvFat->sequenceNumber = lfnSequenceNumber | (dirWriteIdx == 0 ? 0x40 : 0); // Set LFN name block - //DEBUG_ECHOLNPGM("setLFNName for '", (char *) dlname, "', Seq: ", lfnSequenceNumber, ", index: ", index, ", block: ", dirBlock_); setLFNName(pvFat, (char *) dlname, lfnSequenceNumber); } else { // Write SFN Entry - //DEBUG_ECHOLNPGM("*_* Adding SFN entry"); // initialize as empty file memset(p, 0, sizeof(*p)); memcpy(p->name, dname, 11); @@ -820,12 +780,9 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_ // write entry to SD dirFile->vol_->cacheSetDirty(); if (!dirFile->vol_->cacheFlush()) return false; - } - } // open entry in cache - //DEBUG_ECHOLNPGM("*_* Opening idx: ", index); return openCachedEntry(index, oflag); } @@ -1016,7 +973,6 @@ bool SdBaseFile::isDirNameLFN(const char *dirname) { The LFN is complete ("Filename.ext") */ bool SdBaseFile::parsePath(const char *path, uint8_t *name, uint8_t *lname, const char **ptrNextPath) { - //DEBUG_ECHOLNPGM("*_* SdBaseFile::parsePath - Path: ", path, ", Length: ", strlen(path)); // Init randomizer for SFN generation randomSeed(millis()); // Parse the LFN @@ -1043,13 +999,11 @@ bool SdBaseFile::parsePath(const char *path, uint8_t *name, uint8_t *lname, cons // Terminate LFN lname[ilfn] = 0; - //DEBUG_ECHOLNPGM("*_* SdBaseFile::parsePath - LFN parsed path: ", (char *) lname, ", Length: ", strlen((char *)lname), ", IsLFN:", isDirNameLFN((char *)lname)); - // Parse/generate 8.3 SFN. Will take // until 8 characters for the filename part // until 3 characters for the extension part (if exists) // Add 4 more characters if name part < 3 - // Add '~cnt' if it's a LFN + // Add '~cnt' characters if it's a LFN bool isLFN = isDirNameLFN((char *) lname); uint8_t n = isLFN ? 5 : 7, // Max index for each component of the file: @@ -1097,32 +1051,9 @@ bool SdBaseFile::parsePath(const char *path, uint8_t *name, uint8_t *lname, cons name[iFree++] = random(1,9) + '0'; } -/* - const char *sfnpath = (char *) lname; - // uint8_t n = 7, // Max index until a dot is found - // i = 11; - // while (i) name[--i] = ' '; // Set whole FILENAME.EXT to spaces - while (*sfnpath && *sfnpath != '/') { // For each character, until nul or '/' - uint8_t c = *sfnpath++; // Get char and advance - if (c == '.') { // For a dot... - if (n == 10) return false; // Already moved the max index? fail! - n = 10; // Move the max index for full 8.3 name - i = 8; // Move up to the extension place - } - else { - // Fail for illegal characters - PGM_P p = PSTR("|<>^+=?/[];,*\"\\"); - while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; - if (c < 0x21 || c == 0x7F) return false; // Check non-printable characters - if (i <= n) // Check size for 8.3 format - name[i++] = c + (WITHIN(c, 'a', 'z') ? 'A' - 'a' : 0); // Uppercase required for 8.3 name - } - } -*/ // Check if LFN is needed if (!isLFN) lname[0] = 0; // Zero LFN *ptrNextPath = path; // Set passed pointer to the end - //DEBUG_ECHOLNPGM("*_* SdBaseFile::parsePath, SFN: '", (char *) name, "', LFN: '", (char *) lname, "'"); return name[0] != ' '; // Return true if any name was set } @@ -1152,7 +1083,6 @@ void SdBaseFile::getLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber void SdBaseFile::setLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) { uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH; uint8_t nameLength = strlen(lname); - //DEBUG_ECHOLNPGM("*_* SdBaseFile::setLFNName - Starting from pos=", startOffset, " (seq ", sequenceNumber,") of ", lname); LOOP_L_N(i, FILENAME_LENGTH) { uint16_t ch = 0; if ((startOffset + i) < nameLength) @@ -1574,7 +1504,6 @@ dir_t* SdBaseFile::readDirCache() { bool SdBaseFile::remove() { if (ENABLED(SDCARD_READONLY)) return false; - //DEBUG_ECHOLNPGM("*_* SdBaseFile::remove - CurrPosition: ", curPosition_, ", dirIndex_: ", dirIndex_, ", dirBlock_: ", dirBlock_); // free any clusters - will fail if read-only or directory if (!truncate(0)) return false; @@ -1594,31 +1523,22 @@ bool SdBaseFile::remove() { // write entry to SD if (!vol_->cacheFlush()) return false; - //DEBUG_ECHOLNPGM("*_* SdBaseFile::remove - SFN deleted @ Idx: ", dirIndex_); // Check if the entry has a LFN - //DEBUG_ECHOLNPGM("*_* Original - name:", (char *) d->name, ", checksum: ", sfn_checksum, ", dirIndex_: ", dirIndex_); bool lastEntry = false; // loop back to search for any LFN entries related to this file LOOP_S_LE_N(sequenceNumber, 1, MAX_VFAT_ENTRIES) { dirIndex_ = (dirIndex_ - 1) & 0xF; if (dirBlock_ == 0) break; if (dirIndex_ == 0xF) dirBlock_--; - //DEBUG_ECHOLNPGM("*_* Seeking to previous entry from dirIndex_: ", dirIndex_, ", dirBlock_: ", dirBlock_); dir_t *dir = cacheDirEntry(SdVolume::CACHE_FOR_WRITE); - if (!dir) { - //DEBUG_ECHOLNPGM("*_* cacheDirEntry Failed"); - return false; - } + if (!dir) return false; // check for valid LFN: not deleted, not top dirs (".", ".."), must be a LFN if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.' || !isDirLFN(dir)) break; // check coherent LFN: checksum and sequenceNumber must match - //DEBUG_ECHOLNPGM("*_* Found LFN- name:", (char *) dir->name, ", Index: ", dirIndex_); vfat_t* dirlfn = (vfat_t*) dir; - //DEBUG_ECHOLNPGM("*_* PRE Info - Index: ", dirIndex_, ", dirIndex_: ", dirIndex_, ", dirBlock_: ", dirBlock_,", sequenceNumber: ", dirlfn->sequenceNumber, ", checksum: ", dirlfn->checksum); if (dirlfn->checksum != sfn_checksum || (dirlfn->sequenceNumber & 0x1F) != sequenceNumber) break; // orphan entry - //DEBUG_ECHOLNPGM("*_* DEL Info - Index: ", dirIndex_, ", dirIndex_: ", dirIndex_, ", dirBlock_: ", dirBlock_,", sequenceNumber: ", dirlfn->sequenceNumber, ", checksum: ", dirlfn->checksum); // is last entry of LFN ? lastEntry = (dirlfn->sequenceNumber & 0x40); // mark as deleted @@ -1626,10 +1546,7 @@ bool SdBaseFile::remove() { // Flush to SD if (!vol_->cacheFlush()) return false; // exit on last entry of LFN deleted - if (lastEntry) { - //DEBUG_ECHOLNPGM("*_* SdBaseFile::remove - LFN Last entry deleted @ Idx: ", dirIndex_); - break; - } + if (lastEntry) break; } // Restore current index From 7f33fc57e22c3151673df5a8d421047e5a75efb3 Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Sat, 15 Jan 2022 03:24:56 +0100 Subject: [PATCH 05/17] Follow LONG_FILENAME_HOST_SUPPORT in firmware upload --- buildroot/share/scripts/upload.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/buildroot/share/scripts/upload.py b/buildroot/share/scripts/upload.py index 610366ffb4f5..37671c0199ae 100644 --- a/buildroot/share/scripts/upload.py +++ b/buildroot/share/scripts/upload.py @@ -128,6 +128,7 @@ def _RemoveFirmwareFile(FirmwareFile): marlin_motherboard = _GetMarlinEnv(MarlinEnv, 'MOTHERBOARD') marlin_board_info_name = _GetMarlinEnv(MarlinEnv, 'BOARD_INFO_NAME') marlin_board_custom_build_flags = _GetMarlinEnv(MarlinEnv, 'BOARD_CUSTOM_BUILD_FLAGS') + marlin_long_filename_host_support = _GetMarlinEnv(MarlinEnv, 'LONG_FILENAME_HOST_SUPPORT') marlin_firmware_bin = _GetMarlinEnv(MarlinEnv, 'FIRMWARE_BIN') marlin_custom_firmware_upload = _GetMarlinEnv(MarlinEnv, 'CUSTOM_FIRMWARE_UPLOAD') is not None marlin_short_build_version = _GetMarlinEnv(MarlinEnv, 'SHORT_BUILD_VERSION') @@ -203,13 +204,13 @@ def _RemoveFirmwareFile(FirmwareFile): _CheckSDCard() # Get firmware files - FirmwareFiles = _GetFirmwareFiles(marlin_custom_firmware_upload) + FirmwareFiles = _GetFirmwareFiles(marlin_long_filename_host_support) if Debug: for FirmwareFile in FirmwareFiles: print(f'Found: {FirmwareFile}') # Get all 1st level firmware files (to remove) - OldFirmwareFiles = _FilterFirmwareFiles(FirmwareFiles[1:len(FirmwareFiles)-2], marlin_custom_firmware_upload) # Skip header and footers of list + OldFirmwareFiles = _FilterFirmwareFiles(FirmwareFiles[1:len(FirmwareFiles)-2], marlin_long_filename_host_support) # Skip header and footers of list if len(OldFirmwareFiles) == 0: print('No old firmware files to delete') else: From 74e7d6573948e5393b2d75eaadff402f1cd1e291 Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Sat, 15 Jan 2022 04:16:21 +0100 Subject: [PATCH 06/17] Flag for random filename in firmware upload --- buildroot/share/scripts/upload.py | 54 +++++++++++++++++-------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/buildroot/share/scripts/upload.py b/buildroot/share/scripts/upload.py index 37671c0199ae..80b58fb31655 100644 --- a/buildroot/share/scripts/upload.py +++ b/buildroot/share/scripts/upload.py @@ -128,8 +128,8 @@ def _RemoveFirmwareFile(FirmwareFile): marlin_motherboard = _GetMarlinEnv(MarlinEnv, 'MOTHERBOARD') marlin_board_info_name = _GetMarlinEnv(MarlinEnv, 'BOARD_INFO_NAME') marlin_board_custom_build_flags = _GetMarlinEnv(MarlinEnv, 'BOARD_CUSTOM_BUILD_FLAGS') - marlin_long_filename_host_support = _GetMarlinEnv(MarlinEnv, 'LONG_FILENAME_HOST_SUPPORT') marlin_firmware_bin = _GetMarlinEnv(MarlinEnv, 'FIRMWARE_BIN') + marlin_long_filename_host_support = _GetMarlinEnv(MarlinEnv, 'LONG_FILENAME_HOST_SUPPORT') is not None marlin_custom_firmware_upload = _GetMarlinEnv(MarlinEnv, 'CUSTOM_FIRMWARE_UPLOAD') is not None marlin_short_build_version = _GetMarlinEnv(MarlinEnv, 'SHORT_BUILD_VERSION') marlin_string_config_h_author = _GetMarlinEnv(MarlinEnv, 'STRING_CONFIG_H_AUTHOR') @@ -154,6 +154,10 @@ def _RemoveFirmwareFile(FirmwareFile): # "upload_delete_old_bins": delete all *.bin files in the root of SD Card upload_delete_old_bins = marlin_motherboard in ['BOARD_CREALITY_V4', 'BOARD_CREALITY_V4210', 'BOARD_CREALITY_V423', 'BOARD_CREALITY_V427', 'BOARD_CREALITY_V431', 'BOARD_CREALITY_V452', 'BOARD_CREALITY_V453', 'BOARD_CREALITY_V24S1'] + # "upload_random_name": generate a random 8.3 firmware filename to upload + upload_random_filename = marlin_motherboard in ['BOARD_CREALITY_V4', 'BOARD_CREALITY_V4210', 'BOARD_CREALITY_V423', 'BOARD_CREALITY_V427', + 'BOARD_CREALITY_V431', 'BOARD_CREALITY_V452', 'BOARD_CREALITY_V453', 'BOARD_CREALITY_V24S1'] and not marlin_long_filename_host_support + try: # Start upload job @@ -163,27 +167,32 @@ def _RemoveFirmwareFile(FirmwareFile): if Debug: print('Upload using:') print('---- Marlin --------------------') - print(f' PIOENV : {marlin_pioenv}') - print(f' SHORT_BUILD_VERSION : {marlin_short_build_version}') - print(f' STRING_CONFIG_H_AUTHOR : {marlin_string_config_h_author}') - print(f' MOTHERBOARD : {marlin_motherboard}') - print(f' BOARD_INFO_NAME : {marlin_board_info_name}') - print(f' CUSTOM_BUILD_FLAGS : {marlin_board_custom_build_flags}') - print(f' FIRMWARE_BIN : {marlin_firmware_bin}') - print(f' CUSTOM_FIRMWARE_UPLOAD : {marlin_custom_firmware_upload}') - print('---- Upload parameters ---------') - print(f' Source : {upload_firmware_source_name}') - print(f' Target : {upload_firmware_target_name}') - print(f' Port : {upload_port} @ {upload_speed} baudrate') - print(f' Timeout : {upload_timeout}') - print(f' Block size : {upload_blocksize}') - print(f' Compression : {upload_compression}') - print(f' Error ratio : {upload_error_ratio}') - print(f' Test : {upload_test}') - print(f' Reset : {upload_reset}') - print('--------------------------------') + print(f' PIOENV : {marlin_pioenv}') + print(f' SHORT_BUILD_VERSION : {marlin_short_build_version}') + print(f' STRING_CONFIG_H_AUTHOR : {marlin_string_config_h_author}') + print(f' MOTHERBOARD : {marlin_motherboard}') + print(f' BOARD_INFO_NAME : {marlin_board_info_name}') + print(f' CUSTOM_BUILD_FLAGS : {marlin_board_custom_build_flags}') + print(f' FIRMWARE_BIN : {marlin_firmware_bin}') + print(f' LONG_FILENAME_HOST_SUPPORT : {marlin_long_filename_host_support}') + print(f' CUSTOM_FIRMWARE_UPLOAD : {marlin_custom_firmware_upload}') + print('---- Upload parameters ------------------------') + print(f' Source : {upload_firmware_source_name}') + print(f' Target : {upload_firmware_target_name}') + print(f' Port : {upload_port} @ {upload_speed} baudrate') + print(f' Timeout : {upload_timeout}') + print(f' Block size : {upload_blocksize}') + print(f' Compression : {upload_compression}') + print(f' Error ratio : {upload_error_ratio}') + print(f' Test : {upload_test}') + print(f' Reset : {upload_reset}') + print('-----------------------------------------------') # Custom implementations based on board parameters + # Generate a new 8.3 random filename + if upload_random_filename: + upload_firmware_target_name = f"fw-{''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=5))}.BIN" + print(f"Board {marlin_motherboard}: Overriding firmware filename to '{upload_firmware_target_name}'") # Delete all *.bin files on the root of SD Card (if flagged) if upload_delete_old_bins: @@ -191,11 +200,6 @@ def _RemoveFirmwareFile(FirmwareFile): if not marlin_custom_firmware_upload: raise Exception(f"CUSTOM_FIRMWARE_UPLOAD must be enabled in 'Configuration_adv.h' for '{marlin_motherboard}'") - # Generate a new 8.3 random filename - # This board remember the last firmware filename and doesn't allow to flash from that filename - #upload_firmware_target_name = f"fw-{''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=5))}.BIN" - #print(f"Board {marlin_motherboard}: Overriding firmware filename to '{upload_firmware_target_name}'") - # Init serial port port = serial.Serial(upload_port, baudrate = upload_speed, write_timeout = 0, timeout = 0.1) port.reset_input_buffer() From af15ea0cf46060417c2d9a14289aaaabd21ed051 Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Mon, 17 Jan 2022 03:15:47 -0600 Subject: [PATCH 07/17] LONG_FILENAME_WRITE_SUPPORT (costs >2KB flash) --- Marlin/Configuration_adv.h | 3 + Marlin/src/sd/SdBaseFile.cpp | 772 +++++++++++++++++++---------------- Marlin/src/sd/SdBaseFile.h | 33 +- buildroot/tests/mega2560 | 2 +- 4 files changed, 455 insertions(+), 355 deletions(-) diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index dae41f814fe1..7e7e02d9afe8 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1526,6 +1526,9 @@ // LCD's font must contain the characters. Check your selected LCD language. //#define UTF_FILENAME_SUPPORT + // This allows Marlin to create files with long filenames + //#define LONG_FILENAME_WRITE_SUPPORT + // This allows hosts to request long names for files and folders with M33 //#define LONG_FILENAME_HOST_SUPPORT diff --git a/Marlin/src/sd/SdBaseFile.cpp b/Marlin/src/sd/SdBaseFile.cpp index f1cdf6c4ab52..cfbc8fe31354 100644 --- a/Marlin/src/sd/SdBaseFile.cpp +++ b/Marlin/src/sd/SdBaseFile.cpp @@ -441,11 +441,14 @@ bool SdBaseFile::mkdir(SdBaseFile *parent, const char *path, bool pFlag) { if (ENABLED(SDCARD_READONLY)) return false; uint8_t dname[11]; - uint8_t dlname[LONG_FILENAME_LENGTH]; SdBaseFile dir1, dir2; SdBaseFile *sub = &dir1; SdBaseFile *start = parent; + #if ENABLED(LONG_FILENAME_WRITE_SUPPORT) + uint8_t dlname[LONG_FILENAME_LENGTH]; + #endif + if (!parent || isOpen()) return false; if (*path == '/') { @@ -455,29 +458,31 @@ bool SdBaseFile::mkdir(SdBaseFile *parent, const char *path, bool pFlag) { parent = &dir2; } } - while (1) { - //if (!make83Name(path, dname, &path)) return false; - if (!parsePath(path, dname, dlname, &path)) return false; + + for (;;) { + if (!TERN(LONG_FILENAME_WRITE_SUPPORT, parsePath(path, dname, dlname, &path), make83Name(path, dname, &path))) return false; while (*path == '/') path++; if (!*path) break; - if (!sub->open(parent, dname, dlname, O_READ)) { - if (!pFlag || !sub->mkdir(parent, dname, dlname)) + if (!sub->open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), O_READ)) { + if (!pFlag || !sub->mkdir(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname))) return false; } if (parent != start) parent->close(); parent = sub; sub = parent != &dir1 ? &dir1 : &dir2; } - return mkdir(parent, dname, dlname); + return mkdir(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname)); } -bool SdBaseFile::mkdir(SdBaseFile *parent, const uint8_t dname[11], const uint8_t dlname[LONG_FILENAME_LENGTH]) { +bool SdBaseFile::mkdir(SdBaseFile *parent, const uint8_t dname[11] + OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH]) +) { if (ENABLED(SDCARD_READONLY)) return false; if (!parent->isDir()) return false; // create a normal file - if (!open(parent, dname, dlname, O_CREAT | O_EXCL | O_RDWR)) return false; + if (!open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), O_CREAT | O_EXCL | O_RDWR)) return false; // convert file to directory flags_ = O_READ; @@ -592,10 +597,13 @@ bool SdBaseFile::open(const char *path, uint8_t oflag) { */ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) { uint8_t dname[11]; - uint8_t dlname[LONG_FILENAME_LENGTH]; SdBaseFile dir1, dir2; SdBaseFile *parent = dirFile, *sub = &dir1; + #if ENABLED(LONG_FILENAME_WRITE_SUPPORT) + uint8_t dlname[LONG_FILENAME_LENGTH]; + #endif + if (!dirFile || isOpen()) return false; if (*path == '/') { // Path starts with '/' @@ -607,33 +615,40 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const char *path, uint8_t oflag) { } for (;;) { - if (!parsePath(path, dname, dlname, &path)) return false; + if (!TERN(LONG_FILENAME_WRITE_SUPPORT, parsePath(path, dname, dlname, &path), make83Name(path, dname, &path))) return false; while (*path == '/') path++; if (!*path) break; - if (!sub->open(parent, dname, dlname, O_READ)) return false; + if (TERN0(LONG_FILENAME_WRITE_SUPPORT, !sub->open(parent, dname, dlname, O_READ))) return false; if (parent != dirFile) parent->close(); parent = sub; sub = parent != &dir1 ? &dir1 : &dir2; } - return open(parent, dname, dlname, oflag); + return open(parent, dname OPTARG(LONG_FILENAME_WRITE_SUPPORT, dlname), oflag); } // open with filename in dname and long filename in dlname -bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_t dlname[LONG_FILENAME_LENGTH], uint8_t oflag) { +bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11] + OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH]) + , uint8_t oflag +) { bool emptyFound = false, fileFound = false; uint8_t index = 0; dir_t *p; - // LFN - Long File Name support - bool useLFN = dlname[0] != 0, lfnFileFound = false; - vfat_t *pvFat; - uint8_t emptyCount = 0; - uint8_t emptyIndex = 0; - uint8_t reqEntriesNum = useLFN ? getLFNEntriesNum((char *) dlname) + 1 : 1; - uint8_t lfnNameLength = useLFN ? strlen((char *) dlname) : 0; - uint8_t lfnName[LONG_FILENAME_LENGTH]; - uint8_t lfnSequenceNumber = 0; - uint8_t lfnChecksum = 0; - + + #if ENABLED(LONG_FILENAME_WRITE_SUPPORT) + // LFN - Long File Name support + const bool useLFN = dlname[0] != 0; + bool lfnFileFound = false; + vfat_t *pvFat; + uint8_t emptyCount = 0, + emptyIndex = 0, + reqEntriesNum = useLFN ? getLFNEntriesNum((char*)dlname) + 1 : 1, + lfnNameLength = useLFN ? strlen((char*)dlname) : 0, + lfnName[LONG_FILENAME_LENGTH], + lfnSequenceNumber = 0, + lfnChecksum = 0; + #endif + // Rewind this dir vol_ = dirFile->vol_; dirFile->rewind(); @@ -641,146 +656,203 @@ bool SdBaseFile::open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_ // search for file while (dirFile->curPosition_ < dirFile->fileSize_) { // Get absolute index position - index = dirFile->curPosition_ >> 5; + index = (dirFile->curPosition_ >> 5) IF_DISABLED(LONG_FILENAME_WRITE_SUPPORT, & 0x0F); + // Get next entry - p = dirFile->readDirCache(); - if (!p) return false; + if (!(p = dirFile->readDirCache())) return false; - // Check empty status - // Is entry empty? + // Check empty status: Is entry empty? if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) { // Count the contiguous available entries in which (eventually) fit the new dir entry, if it's a write operation if (!emptyFound) { - if (emptyCount == 0) emptyIndex = index; - // Incr empty entries counter - emptyCount++; - // If found the required empty entries, mark it - if (emptyCount == reqEntriesNum) { + #if ENABLED(LONG_FILENAME_WRITE_SUPPORT) + if (emptyCount == 0) emptyIndex = index; + // Incr empty entries counter + // If found the required empty entries, mark it + if (++emptyCount == reqEntriesNum) { + dirBlock_ = dirFile->vol_->cacheBlockNumber(); + dirIndex_ = index & 0xF; + emptyFound = true; + } + #else dirBlock_ = dirFile->vol_->cacheBlockNumber(); - dirIndex_ = index & 0xF; + dirIndex_ = index; emptyFound = true; - } + #endif } // Done if no entries follow if (p->name[0] == DIR_NAME_FREE) break; } else { // Entry not empty - // Reset empty counter - if (!emptyFound) emptyCount = 0; - // Search for SFN or LFN? - if (!useLFN) { - // Check using SFN: file found? - if (!memcmp(dname, p->name, 11)) { - fileFound = true; - break; + #if ENABLED(LONG_FILENAME_WRITE_SUPPORT) + // Reset empty counter + if (!emptyFound) emptyCount = 0; + // Search for SFN or LFN? + if (!useLFN) { + // Check using SFN: file found? + if (!memcmp(dname, p->name, 11)) { + fileFound = true; + break; + } } - } - else { - // Check using LFN: LFN not found? continue search for LFN - if (!lfnFileFound) { - // Is this dir a LFN? - if (isDirLFN(p)) { - // Get VFat dir entry - pvFat = (vfat_t *) p; - // Get checksum from the last entry of the sequence - if (pvFat->sequenceNumber & 0x40) lfnChecksum = pvFat->checksum; - // Get LFN sequence number - lfnSequenceNumber = pvFat->sequenceNumber & 0x1F; - if WITHIN(lfnSequenceNumber, 1, reqEntriesNum) { - // Check checksum for all other entries with the starting checksum fetched before - if (lfnChecksum == pvFat->checksum) { - // Set chunk of LFN from VFAT entry into lfnName - getLFNName(pvFat, (char *)lfnName, lfnSequenceNumber); - // LFN found? - if (!strncasecmp((char *) dlname, (char *) lfnName, lfnNameLength)) lfnFileFound = true; + else { + // Check using LFN: LFN not found? continue search for LFN + if (!lfnFileFound) { + // Is this dir a LFN? + if (isDirLFN(p)) { + // Get VFat dir entry + pvFat = (vfat_t *) p; + // Get checksum from the last entry of the sequence + if (pvFat->sequenceNumber & 0x40) lfnChecksum = pvFat->checksum; + // Get LFN sequence number + lfnSequenceNumber = pvFat->sequenceNumber & 0x1F; + if WITHIN(lfnSequenceNumber, 1, reqEntriesNum) { + // Check checksum for all other entries with the starting checksum fetched before + if (lfnChecksum == pvFat->checksum) { + // Set chunk of LFN from VFAT entry into lfnName + getLFNName(pvFat, (char *)lfnName, lfnSequenceNumber); + // LFN found? + if (!strncasecmp((char*)dlname, (char*)lfnName, lfnNameLength)) lfnFileFound = true; + } } } } + else { // Complete LFN found, check for related SFN + // Check if only the SFN checksum match because the filename may be different due to different truncation methods + if (!isDirLFN(p) && (lfnChecksum == lfn_checksum(p->name))) { + fileFound = true; + break; + } + else lfnFileFound = false; // SFN not valid for the LFN found, reset LFN FileFound + } } - else { // Complete LFN found, check for related SFN - // Check if only the SFN checksum match because the filename may be different due to different truncation methods - if (!isDirLFN(p) && (lfnChecksum == lfn_checksum(p->name))) { - fileFound = true; - break; - } - else lfnFileFound = false; // SFN not valid for the LFN found, reset LFN FileFound + #else + + if (!memcmp(dname, p->name, 11)) { + fileFound = true; + break; } - } + + #endif // LONG_FILENAME_WRITE_SUPPORT } } + if (fileFound) { // don't open existing file if O_EXCL if (oflag & O_EXCL) return false; - index &= 0xF; + TERN_(LONG_FILENAME_WRITE_SUPPORT, index &= 0xF); } else { // don't create unless O_CREAT and O_WRITE if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) return false; - // Use bookmark index if found empty entries - if (emptyFound) index = emptyIndex; + #if ENABLED(LONG_FILENAME_WRITE_SUPPORT) - // Make room for needed entries - while (emptyCount < reqEntriesNum) - { + // Use bookmark index if found empty entries + if (emptyFound) index = emptyIndex; + + // Make room for needed entries + while (emptyCount < reqEntriesNum) { p = dirFile->readDirCache(); if (!p) break; emptyCount++; - } - while (emptyCount < reqEntriesNum) - { + } + while (emptyCount < reqEntriesNum) { if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false; // add and zero cluster for dirFile - first cluster is in cache for write if (!dirFile->addDirCluster()) return false; emptyCount += dirFile->vol_->blocksPerCluster() * 16; - } - - // Move to 1st entry to write - if (!dirFile->seekSet(32 * index)) return false; - - // Dir entries write loop: [LFN] + SFN(1) - LOOP_L_N(dirWriteIdx, reqEntriesNum) { - index = (dirFile->curPosition_ / 32) & 0xF; - p = dirFile->readDirCache(); - // LFN or SFN Entry? - if (dirWriteIdx < reqEntriesNum - 1) { - // Write LFN Entries - pvFat = (vfat_t *) p; - // initialize as empty file - memset(pvFat, 0, sizeof(*pvFat)); - lfnSequenceNumber = (reqEntriesNum - dirWriteIdx - 1) & 0x1F; - pvFat->attributes = DIR_ATT_LONG_NAME; - pvFat->checksum = lfn_checksum(dname); - // Set sequence number and mark as last LFN entry if it's the 1st loop - pvFat->sequenceNumber = lfnSequenceNumber | (dirWriteIdx == 0 ? 0x40 : 0); - // Set LFN name block - setLFNName(pvFat, (char *) dlname, lfnSequenceNumber); } - else { - // Write SFN Entry - // initialize as empty file - memset(p, 0, sizeof(*p)); - memcpy(p->name, dname, 11); - - // set timestamps - if (dateTime_) { - // call user date/time function - dateTime_(&p->creationDate, &p->creationTime); + + // Move to 1st entry to write + if (!dirFile->seekSet(32 * index)) return false; + + // Dir entries write loop: [LFN] + SFN(1) + LOOP_L_N(dirWriteIdx, reqEntriesNum) { + index = (dirFile->curPosition_ / 32) & 0xF; + p = dirFile->readDirCache(); + // LFN or SFN Entry? + if (dirWriteIdx < reqEntriesNum - 1) { + // Write LFN Entries + pvFat = (vfat_t *) p; + // initialize as empty file + memset(pvFat, 0, sizeof(*pvFat)); + lfnSequenceNumber = (reqEntriesNum - dirWriteIdx - 1) & 0x1F; + pvFat->attributes = DIR_ATT_LONG_NAME; + pvFat->checksum = lfn_checksum(dname); + // Set sequence number and mark as last LFN entry if it's the 1st loop + pvFat->sequenceNumber = lfnSequenceNumber | (dirWriteIdx == 0 ? 0x40 : 0); + // Set LFN name block + setLFNName(pvFat, (char*)dlname, lfnSequenceNumber); } else { - // use default date/time - p->creationDate = FAT_DEFAULT_DATE; - p->creationTime = FAT_DEFAULT_TIME; + // Write SFN Entry + // initialize as empty file + memset(p, 0, sizeof(*p)); + memcpy(p->name, dname, 11); + + // set timestamps + if (dateTime_) { + // call user date/time function + dateTime_(&p->creationDate, &p->creationTime); + } + else { + // use default date/time + p->creationDate = FAT_DEFAULT_DATE; + p->creationTime = FAT_DEFAULT_TIME; + } + p->lastAccessDate = p->creationDate; + p->lastWriteDate = p->creationDate; + p->lastWriteTime = p->creationTime; } - p->lastAccessDate = p->creationDate; - p->lastWriteDate = p->creationDate; - p->lastWriteTime = p->creationTime; + + // write entry to SD + dirFile->vol_->cacheSetDirty(); + if (!dirFile->vol_->cacheFlush()) return false; + } + + #else // !LONG_FILENAME_WRITE_SUPPORT + + if (emptyFound) { + index = dirIndex_; + p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE); + if (!p) return false; + } + else { + if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) return false; + + // add and zero cluster for dirFile - first cluster is in cache for write + if (!dirFile->addDirCluster()) return false; + + // use first entry in cluster + p = dirFile->vol_->cache()->dir; + index = 0; + } + + // initialize as empty file + memset(p, 0, sizeof(*p)); + memcpy(p->name, dname, 11); + + // set timestamps + if (dateTime_) { + // call user date/time function + dateTime_(&p->creationDate, &p->creationTime); + } + else { + // use default date/time + p->creationDate = FAT_DEFAULT_DATE; + p->creationTime = FAT_DEFAULT_TIME; } + p->lastAccessDate = p->creationDate; + p->lastWriteDate = p->creationDate; + p->lastWriteTime = p->creationTime; + // write entry to SD - dirFile->vol_->cacheSetDirty(); if (!dirFile->vol_->cacheFlush()) return false; - } + + #endif // !LONG_FILENAME_WRITE_SUPPORT + } // open entry in cache return openCachedEntry(index, oflag); @@ -916,189 +988,190 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) { return false; } - -/** - * Check if dir is a long file name entry (LFN) - * - * \param[in] dir Parent of this directory will be opened. Must not be root. - * \return true if the dir is a long file name entry (LFN) - */ -bool SdBaseFile::isDirLFN(const dir_t* dir) { - if (DIR_IS_LONG_NAME(dir)) { - vfat_t *VFAT = (vfat_t*)dir; - // Sanity-check the VFAT entry. The first cluster is always set to zero. And the sequence number should be higher than 0 - if ((VFAT->firstClusterLow == 0) && WITHIN((VFAT->sequenceNumber & 0x1F), 1, MAX_VFAT_ENTRIES)) return true; +#if ENABLED(LONG_FILENAME_WRITE_SUPPORT) + + /** + * Check if dir is a long file name entry (LFN) + * + * \param[in] dir Parent of this directory will be opened. Must not be root. + * \return true if the dir is a long file name entry (LFN) + */ + bool SdBaseFile::isDirLFN(const dir_t* dir) { + if (DIR_IS_LONG_NAME(dir)) { + vfat_t *VFAT = (vfat_t*)dir; + // Sanity-check the VFAT entry. The first cluster is always set to zero. And the sequence number should be higher than 0 + if ((VFAT->firstClusterLow == 0) && WITHIN((VFAT->sequenceNumber & 0x1F), 1, MAX_VFAT_ENTRIES)) return true; + } + return false; } - return false; -} -/** - * Check if dirname string is a long file name (LFN) - * - * \param[in] dirname The string to check - * \return true if the dirname is a long file name (LFN) - * \return false if the dirname is a short file name 8.3 (SFN) - */ -bool SdBaseFile::isDirNameLFN(const char *dirname) { - uint8_t length = strlen(dirname); - uint8_t idx = length; - bool dotFound = false; - if (idx > 12) return true; // LFN due to filename length > 12 ("filename.ext") - // Check dot(s) position - while (idx) { - if (dirname[--idx] == '.') { - if (!dotFound) { - // Last dot (extension) is allowed only - // in position [1..8] from start or [0..3] from end for SFN else it's a LFN - // A filename starting with "." is a LFN (eg. ".file" ->in SFN-> "file~1 ") - // A filename ending with "." is a SFN (if length <= 9) (eg. "file." ->in SFN-> "file ") - if (idx > 8 || idx == 0 || (length - idx - 1) > 3) return true; // LFN due to dot extension position - dotFound = true; - } - else { - // Found another dot, is a LFN - return true; + /** + * Check if dirname string is a long file name (LFN) + * + * \param[in] dirname The string to check + * \return true if the dirname is a long file name (LFN) + * \return false if the dirname is a short file name 8.3 (SFN) + */ + bool SdBaseFile::isDirNameLFN(const char *dirname) { + uint8_t length = strlen(dirname); + uint8_t idx = length; + bool dotFound = false; + if (idx > 12) return true; // LFN due to filename length > 12 ("filename.ext") + // Check dot(s) position + while (idx) { + if (dirname[--idx] == '.') { + if (!dotFound) { + // Last dot (extension) is allowed only + // in position [1..8] from start or [0..3] from end for SFN else it's a LFN + // A filename starting with "." is a LFN (eg. ".file" ->in SFN-> "file~1 ") + // A filename ending with "." is a SFN (if length <= 9) (eg. "file." ->in SFN-> "file ") + if (idx > 8 || idx == 0 || (length - idx - 1) > 3) return true; // LFN due to dot extension position + dotFound = true; + } + else { + // Found another dot, is a LFN + return true; + } } } + // If no dots found, the filename must be of max 8 characters + if ((!dotFound) && length > 8) return true; // LFN due to max filename (without extension) length + return false; } - // If no dots found, the filename must be of max 8 characters - if ((!dotFound) && length > 8) return true; // LFN due to max filename (without extension) length - return false; -} - -/* - Parse path and return 8.3 format and LFN filenames (if the parsed path is a LFN) - The SFN is without dot ("FILENAMEEXT") - The LFN is complete ("Filename.ext") -*/ -bool SdBaseFile::parsePath(const char *path, uint8_t *name, uint8_t *lname, const char **ptrNextPath) { - // Init randomizer for SFN generation - randomSeed(millis()); - // Parse the LFN - uint8_t ilfn = 0; - bool lastDotFound = false; - const char *pLastDot = 0; - const char *lfnpath = path; - uint8_t c; - - while (*lfnpath && *lfnpath != '/') { - if (ilfn == LONG_FILENAME_LENGTH - 1) return false; // Name too long - c = *lfnpath++; // Get char and advance - // Fail for illegal characters - PGM_P p = PSTR("|<>^+=?/[];:,*\"\\"); - while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; // Check reserved characters - if (c < 0x20 || c == 0x7F) return false; // Check non-printable characters - if (c == '.' && (lfnpath - 1) > path) { // Skip dot '.' check in 1st position - // Save last dot pointer (skip if starts with '.') - pLastDot = lfnpath - 1; - lastDotFound = true; + /** + * Parse path and return 8.3 format and LFN filenames (if the parsed path is a LFN) + * The SFN is without dot ("FILENAMEEXT") + * The LFN is complete ("Filename.ext") + */ + bool SdBaseFile::parsePath(const char *path, uint8_t *name, uint8_t *lname, const char **ptrNextPath) { + // Init randomizer for SFN generation + randomSeed(millis()); + // Parse the LFN + uint8_t ilfn = 0; + bool lastDotFound = false; + const char *pLastDot = 0; + const char *lfnpath = path; + uint8_t c; + + while (*lfnpath && *lfnpath != '/') { + if (ilfn == LONG_FILENAME_LENGTH - 1) return false; // Name too long + c = *lfnpath++; // Get char and advance + // Fail for illegal characters + PGM_P p = PSTR("|<>^+=?/[];:,*\"\\"); + while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; // Check reserved characters + if (c < 0x20 || c == 0x7F) return false; // Check non-printable characters + if (c == '.' && (lfnpath - 1) > path) { // Skip dot '.' check in 1st position + // Save last dot pointer (skip if starts with '.') + pLastDot = lfnpath - 1; + lastDotFound = true; + } + lname[ilfn++] = c; // Set LFN character } - lname[ilfn++] = c; // Set LFN character - } - // Terminate LFN - lname[ilfn] = 0; - - // Parse/generate 8.3 SFN. Will take - // until 8 characters for the filename part - // until 3 characters for the extension part (if exists) - // Add 4 more characters if name part < 3 - // Add '~cnt' characters if it's a LFN - bool isLFN = isDirNameLFN((char *) lname); - - uint8_t n = isLFN ? 5 : 7, // Max index for each component of the file: - // starting with 7 or 5 (if LFN) - // switch to 10 for extension if the last dot is found - i = 11; - while (i) name[--i] = ' '; // Set whole FILENAMEEXT to spaces - while (*path && *path != '/') { - c = *path++; // Get char and advance - // Skip spaces and dots (if it's not the last dot) - if (c == ' ') continue; - if (c == '.' && (!lastDotFound || (lastDotFound && path < pLastDot))) continue; - // Fail for illegal characters - PGM_P p = PSTR("|<>^+=?/[];:,*\"\\"); - while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; // Check reserved characters - if (c < 0x21 || c == 0x7F) return false; // Check non-printable characters - // Is last dot? - if (c == '.') { - // Switch to extension part - n = 10; - i = 8; - } - // If in valid range add the character - else if (i <= n) // Check size for 8.3 format - name[i++] = c + (WITHIN(c, 'a', 'z') ? 'A' - 'a' : 0); // Uppercase required for 8.3 name - } - // If it's a LFN then the SFN always need: - // - A minimal of 3 characters (otherwise 4 chars are added) - // - The '~cnt' at the end - if (isLFN) { - // Get the 1st free character - uint8_t iFree = 0; - while (1) if (name[iFree++] == ' ' || iFree == 11) break; - iFree--; - // Check minimal length - if (iFree < 3) { - // Append 4 extra characters - name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(1,9) + 'A'; - name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(1,9) + 'A'; + // Terminate LFN + lname[ilfn] = 0; + + // Parse/generate 8.3 SFN. Will take + // until 8 characters for the filename part + // until 3 characters for the extension part (if exists) + // Add 4 more characters if name part < 3 + // Add '~cnt' characters if it's a LFN + const bool isLFN = isDirNameLFN((char*)lname); + + uint8_t n = isLFN ? 5 : 7, // Max index for each component of the file: + // starting with 7 or 5 (if LFN) + // switch to 10 for extension if the last dot is found + i = 11; + while (i) name[--i] = ' '; // Set whole FILENAMEEXT to spaces + while (*path && *path != '/') { + c = *path++; // Get char and advance + // Skip spaces and dots (if it's not the last dot) + if (c == ' ') continue; + if (c == '.' && (!lastDotFound || (lastDotFound && path < pLastDot))) continue; + // Fail for illegal characters + PGM_P p = PSTR("|<>^+=?/[];:,*\"\\"); + while (uint8_t b = pgm_read_byte(p++)) if (b == c) return false; // Check reserved characters + if (c < 0x21 || c == 0x7F) return false; // Check non-printable characters + // Is last dot? + if (c == '.') { + // Switch to extension part + n = 10; + i = 8; + } + // If in valid range add the character + else if (i <= n) // Check size for 8.3 format + name[i++] = c + (WITHIN(c, 'a', 'z') ? 'A' - 'a' : 0); // Uppercase required for 8.3 name + } + // If it's a LFN then the SFN always need: + // - A minimal of 3 characters (otherwise 4 chars are added) + // - The '~cnt' at the end + if (isLFN) { + // Get the 1st free character + uint8_t iFree = 0; + while (1) if (name[iFree++] == ' ' || iFree == 11) break; + iFree--; + // Check minimal length + if (iFree < 3) { + // Append 4 extra characters + name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(1,9) + 'A'; + name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(1,9) + 'A'; + } + // Append '~cnt' characters + if (iFree > 5) iFree = 5; // Force the append in the last 3 characters of name part + name[iFree++] = '~'; + name[iFree++] = random(1,9) + '0'; + name[iFree++] = random(1,9) + '0'; } - // Append '~cnt' characters - if (iFree > 5) iFree = 5; // Force the append in the last 3 characters of name part - name[iFree++] = '~'; - name[iFree++] = random(1,9) + '0'; - name[iFree++] = random(1,9) + '0'; - } - // Check if LFN is needed - if (!isLFN) lname[0] = 0; // Zero LFN - *ptrNextPath = path; // Set passed pointer to the end - return name[0] != ' '; // Return true if any name was set -} + // Check if LFN is needed + if (!isLFN) lname[0] = 0; // Zero LFN + *ptrNextPath = path; // Set passed pointer to the end + return name[0] != ' '; // Return true if any name was set + } -/* - Get the LFN filename block from a dir. Get the block in lname at startOffset -*/ -void SdBaseFile::getLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) { - uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH; - LOOP_L_N(i, FILENAME_LENGTH) { - const uint16_t utf16_ch = (i >= 11) ? pFatDir->name3[i - 11] : (i >= 5) ? pFatDir->name2[i - 5] : pFatDir->name1[i]; - #if ENABLED(UTF_FILENAME_SUPPORT) - // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks - // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later. - uint16_t idx = (startOffset + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding - longFilename[idx] = utf16_ch & 0xFF; - longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF; - #else - // Replace all multibyte characters to '_' - lname[startOffset + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF); - #endif + /** + * Get the LFN filename block from a dir. Get the block in lname at startOffset + */ + void SdBaseFile::getLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) { + uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH; + LOOP_L_N(i, FILENAME_LENGTH) { + const uint16_t utf16_ch = (i >= 11) ? pFatDir->name3[i - 11] : (i >= 5) ? pFatDir->name2[i - 5] : pFatDir->name1[i]; + #if ENABLED(UTF_FILENAME_SUPPORT) + // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks + // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later. + uint16_t idx = (startOffset + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding + longFilename[idx] = utf16_ch & 0xFF; + longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF; + #else + // Replace all multibyte characters to '_' + lname[startOffset + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF); + #endif + } } -} -/* - Set the LFN filename block lname to a dir. Put the block based on sequence number -*/ -void SdBaseFile::setLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) { - uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH; - uint8_t nameLength = strlen(lname); - LOOP_L_N(i, FILENAME_LENGTH) { - uint16_t ch = 0; - if ((startOffset + i) < nameLength) - ch = lname[startOffset + i]; - else if ((startOffset + i) > nameLength) - ch = 0xFFFF; - // Set char - if (i < 5) - pFatDir->name1[i] = ch; - else if (i < 11) - pFatDir->name2[i - 5] = ch; - else - pFatDir->name3[i - 11] = ch; + /** + * Set the LFN filename block lname to a dir. Put the block based on sequence number + */ + void SdBaseFile::setLFNName(vfat_t *pFatDir, char *lname, uint8_t sequenceNumber) { + uint8_t startOffset = (sequenceNumber - 1) * FILENAME_LENGTH; + uint8_t nameLength = strlen(lname); + LOOP_L_N(i, FILENAME_LENGTH) { + uint16_t ch = 0; + if ((startOffset + i) < nameLength) + ch = lname[startOffset + i]; + else if ((startOffset + i) > nameLength) + ch = 0xFFFF; + // Set char + if (i < 5) + pFatDir->name1[i] = ch; + else if (i < 11) + pFatDir->name2[i - 5] = ch; + else + pFatDir->name3[i - 11] = ch; + } } -} +#endif // LONG_FILENAME_WRITE_SUPPORT #if 0 /** @@ -1388,7 +1461,6 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) { if (VFAT->firstClusterLow == 0) { const uint8_t seq = VFAT->sequenceNumber & 0x1F; if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) { - //n = (seq - 1) * (FILENAME_LENGTH); if (seq == 1) { checksum = VFAT->checksum; checksum_error = 0; @@ -1396,28 +1468,33 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) { else if (checksum != VFAT->checksum) // orphan detected checksum_error = 1; - // Get chunk of LFN from VFAT entry - //getLFNName(VFAT, longFilename, n); - getLFNName(VFAT, longFilename, seq); - /* - LOOP_L_N(i, FILENAME_LENGTH) { - const uint16_t utf16_ch = (i >= 11) ? VFAT->name3[i - 11] : (i >= 5) ? VFAT->name2[i - 5] : VFAT->name1[i]; - #if ENABLED(UTF_FILENAME_SUPPORT) - // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks - // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later. - uint16_t idx = (n + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding - longFilename[idx] = utf16_ch & 0xFF; - longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF; - #else - // Replace all multibyte characters to '_' - longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF); - #endif - } - */ + #if ENABLED(LONG_FILENAME_WRITE_SUPPORT) + + getLFNName(VFAT, longFilename, seq); // Get chunk of LFN from VFAT entry + + #else // !LONG_FILENAME_WRITE_SUPPORT + + n = (seq - 1) * (FILENAME_LENGTH); + + LOOP_L_N(i, FILENAME_LENGTH) { + const uint16_t utf16_ch = (i >= 11) ? VFAT->name3[i - 11] : (i >= 5) ? VFAT->name2[i - 5] : VFAT->name1[i]; + #if ENABLED(UTF_FILENAME_SUPPORT) + // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks + // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later. + uint16_t idx = (n + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding + longFilename[idx] = utf16_ch & 0xFF; + longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF; + #else + // Replace all multibyte characters to '_' + longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF); + #endif + } + + #endif // !LONG_FILENAME_WRITE_SUPPORT + // If this VFAT entry is the last one, add a NUL terminator at the end of the string if (VFAT->sequenceNumber & 0x40) - longFilename[seq * FILENAME_LENGTH * LONG_FILENAME_CHARSIZE] = '\0'; - //longFilename[(n + FILENAME_LENGTH) * LONG_FILENAME_CHARSIZE] = '\0'; + longFilename[LONG_FILENAME_CHARSIZE * TERN(LONG_FILENAME_WRITE_SUPPORT, seq * FILENAME_LENGTH, (n + FILENAME_LENGTH))] = '\0'; } } } @@ -1511,49 +1588,60 @@ bool SdBaseFile::remove() { dir_t *d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE); if (!d) return false; - // get SFN checksum before name rewrite (needed for LFN deletion) - uint8_t sfn_checksum = lfn_checksum(d->name); + #if ENABLED(LONG_FILENAME_WRITE_SUPPORT) + // get SFN checksum before name rewrite (needed for LFN deletion) + const uint8_t sfn_checksum = lfn_checksum(d->name); + #endif // mark entry deleted d->name[0] = DIR_NAME_DELETED; // set this file closed type_ = FAT_FILE_TYPE_CLOSED; - flags_ = 0; // write entry to SD - if (!vol_->cacheFlush()) return false; + #if DISABLED(LONG_FILENAME_WRITE_SUPPORT) + + return vol_->cacheFlush(); + + #else // LONG_FILENAME_WRITE_SUPPORT + + flags_ = 0; - // Check if the entry has a LFN - bool lastEntry = false; - // loop back to search for any LFN entries related to this file - LOOP_S_LE_N(sequenceNumber, 1, MAX_VFAT_ENTRIES) { - dirIndex_ = (dirIndex_ - 1) & 0xF; - if (dirBlock_ == 0) break; - if (dirIndex_ == 0xF) dirBlock_--; - dir_t *dir = cacheDirEntry(SdVolume::CACHE_FOR_WRITE); - if (!dir) return false; - - // check for valid LFN: not deleted, not top dirs (".", ".."), must be a LFN - if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.' || !isDirLFN(dir)) break; - // check coherent LFN: checksum and sequenceNumber must match - vfat_t* dirlfn = (vfat_t*) dir; - if (dirlfn->checksum != sfn_checksum || (dirlfn->sequenceNumber & 0x1F) != sequenceNumber) break; // orphan entry - // is last entry of LFN ? - lastEntry = (dirlfn->sequenceNumber & 0x40); - // mark as deleted - dirlfn->sequenceNumber = DIR_NAME_DELETED; - // Flush to SD if (!vol_->cacheFlush()) return false; - // exit on last entry of LFN deleted - if (lastEntry) break; - } - // Restore current index - //if (!seekSet(32UL * dirIndex_)) return false; - //dirIndex_ += prevDirIndex; + // Check if the entry has a LFN + bool lastEntry = false; + // loop back to search for any LFN entries related to this file + LOOP_S_LE_N(sequenceNumber, 1, MAX_VFAT_ENTRIES) { + dirIndex_ = (dirIndex_ - 1) & 0xF; + if (dirBlock_ == 0) break; + if (dirIndex_ == 0xF) dirBlock_--; + dir_t *dir = cacheDirEntry(SdVolume::CACHE_FOR_WRITE); + if (!dir) return false; + + // check for valid LFN: not deleted, not top dirs (".", ".."), must be a LFN + if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.' || !isDirLFN(dir)) break; + // check coherent LFN: checksum and sequenceNumber must match + vfat_t* dirlfn = (vfat_t*) dir; + if (dirlfn->checksum != sfn_checksum || (dirlfn->sequenceNumber & 0x1F) != sequenceNumber) break; // orphan entry + // is last entry of LFN ? + lastEntry = (dirlfn->sequenceNumber & 0x40); + // mark as deleted + dirlfn->sequenceNumber = DIR_NAME_DELETED; + // Flush to SD + if (!vol_->cacheFlush()) return false; + // exit on last entry of LFN deleted + if (lastEntry) break; + } - return true; + // Restore current index + //if (!seekSet(32UL * dirIndex_)) return false; + //dirIndex_ += prevDirIndex; + + return true; + + #endif // LONG_FILENAME_WRITE_SUPPORT } /** diff --git a/Marlin/src/sd/SdBaseFile.h b/Marlin/src/sd/SdBaseFile.h index cc912d289645..e953deb6af10 100644 --- a/Marlin/src/sd/SdBaseFile.h +++ b/Marlin/src/sd/SdBaseFile.h @@ -30,6 +30,8 @@ * This file is part of the Arduino Sd2Card Library */ +#include "../inc/MarlinConfig.h" + #include "SdFatConfig.h" #include "SdVolume.h" @@ -377,19 +379,26 @@ class SdBaseFile { dir_t* cacheDirEntry(uint8_t action); int8_t lsPrintNext(uint8_t flags, uint8_t indent); static bool make83Name(const char *str, uint8_t *name, const char **ptr); - bool mkdir(SdBaseFile *parent, const uint8_t dname[11], const uint8_t dlname[LONG_FILENAME_LENGTH]); - bool open(SdBaseFile *dirFile, const uint8_t dname[11], const uint8_t dlname[LONG_FILENAME_LENGTH], uint8_t oflag); + bool mkdir(SdBaseFile *parent, const uint8_t dname[11] + OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH]) + ); + bool open(SdBaseFile *dirFile, const uint8_t dname[11] + OPTARG(LONG_FILENAME_WRITE_SUPPORT, const uint8_t dlname[LONG_FILENAME_LENGTH]) + , uint8_t oflag + ); bool openCachedEntry(uint8_t cacheIndex, uint8_t oflags); dir_t* readDirCache(); - // LFN support - static bool isDirLFN(const dir_t* dir); - static bool isDirNameLFN(const char *dirname); - static bool parsePath(const char *str, uint8_t *name, uint8_t *lname, const char **ptr); - /* - Return the number of entries needed in the FAT for this LFN - */ - static inline uint8_t getLFNEntriesNum(const char *lname) { return (strlen(lname) + 12) / 13; } - static void getLFNName(vfat_t *vFatDir, char *lname, uint8_t startOffset); - static void setLFNName(vfat_t *vFatDir, char *lname, uint8_t lfnSequenceNumber); + // Long Filename create/write support + #if ENABLED(LONG_FILENAME_WRITE_SUPPORT) + static bool isDirLFN(const dir_t* dir); + static bool isDirNameLFN(const char *dirname); + static bool parsePath(const char *str, uint8_t *name, uint8_t *lname, const char **ptr); + /** + * Return the number of entries needed in the FAT for this LFN + */ + static inline uint8_t getLFNEntriesNum(const char *lname) { return (strlen(lname) + 12) / 13; } + static void getLFNName(vfat_t *vFatDir, char *lname, uint8_t startOffset); + static void setLFNName(vfat_t *vFatDir, char *lname, uint8_t lfnSequenceNumber); + #endif }; diff --git a/buildroot/tests/mega2560 b/buildroot/tests/mega2560 index bf3290b9d02c..5ae9a2dbcf0b 100755 --- a/buildroot/tests/mega2560 +++ b/buildroot/tests/mega2560 @@ -35,7 +35,7 @@ opt_set MOTHERBOARD BOARD_AZTEEG_X3_PRO LCD_LANGUAGE jp_kana DEFAULT_EJERK 10 \ EXTRUDERS 5 TEMP_SENSOR_1 1 TEMP_SENSOR_2 5 TEMP_SENSOR_3 20 TEMP_SENSOR_4 1000 TEMP_SENSOR_BED 1 opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER LIGHTWEIGHT_UI SHOW_CUSTOM_BOOTSCREEN BOOT_MARLIN_LOGO_SMALL \ LCD_SET_PROGRESS_MANUALLY PRINT_PROGRESS_SHOW_DECIMALS SHOW_REMAINING_TIME STATUS_MESSAGE_SCROLLING SCROLL_LONG_FILENAMES \ - SDSUPPORT SDCARD_SORT_ALPHA NO_SD_AUTOSTART USB_FLASH_DRIVE_SUPPORT CANCEL_OBJECTS \ + SDSUPPORT LONG_FILENAME_WRITE_SUPPORT SDCARD_SORT_ALPHA NO_SD_AUTOSTART USB_FLASH_DRIVE_SUPPORT CANCEL_OBJECTS \ Z_PROBE_SLED AUTO_BED_LEVELING_UBL UBL_HILBERT_CURVE RESTORE_LEVELING_AFTER_G28 DEBUG_LEVELING_FEATURE G26_MESH_VALIDATION ENABLE_LEVELING_FADE_HEIGHT \ EEPROM_SETTINGS EEPROM_CHITCHAT GCODE_MACROS CUSTOM_MENU_MAIN \ MULTI_NOZZLE_DUPLICATION CLASSIC_JERK LIN_ADVANCE QUICK_HOME \ From 5b9b248ddb78be6a579236da96ee0eeecb408181 Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Mon, 17 Jan 2022 03:26:06 -0600 Subject: [PATCH 08/17] script var --- buildroot/share/scripts/upload.py | 40 ++++++++++++++++--------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/buildroot/share/scripts/upload.py b/buildroot/share/scripts/upload.py index 80b58fb31655..ceb0db4f10ab 100644 --- a/buildroot/share/scripts/upload.py +++ b/buildroot/share/scripts/upload.py @@ -130,6 +130,7 @@ def _RemoveFirmwareFile(FirmwareFile): marlin_board_custom_build_flags = _GetMarlinEnv(MarlinEnv, 'BOARD_CUSTOM_BUILD_FLAGS') marlin_firmware_bin = _GetMarlinEnv(MarlinEnv, 'FIRMWARE_BIN') marlin_long_filename_host_support = _GetMarlinEnv(MarlinEnv, 'LONG_FILENAME_HOST_SUPPORT') is not None + marlin_longname_write = _GetMarlinEnv(MarlinEnv, 'LONG_FILENAME_WRITE_SUPPORT') is not None marlin_custom_firmware_upload = _GetMarlinEnv(MarlinEnv, 'CUSTOM_FIRMWARE_UPLOAD') is not None marlin_short_build_version = _GetMarlinEnv(MarlinEnv, 'SHORT_BUILD_VERSION') marlin_string_config_h_author = _GetMarlinEnv(MarlinEnv, 'STRING_CONFIG_H_AUTHOR') @@ -166,26 +167,27 @@ def _RemoveFirmwareFile(FirmwareFile): # Dump some debug info if Debug: print('Upload using:') - print('---- Marlin --------------------') - print(f' PIOENV : {marlin_pioenv}') - print(f' SHORT_BUILD_VERSION : {marlin_short_build_version}') - print(f' STRING_CONFIG_H_AUTHOR : {marlin_string_config_h_author}') - print(f' MOTHERBOARD : {marlin_motherboard}') - print(f' BOARD_INFO_NAME : {marlin_board_info_name}') - print(f' CUSTOM_BUILD_FLAGS : {marlin_board_custom_build_flags}') - print(f' FIRMWARE_BIN : {marlin_firmware_bin}') - print(f' LONG_FILENAME_HOST_SUPPORT : {marlin_long_filename_host_support}') - print(f' CUSTOM_FIRMWARE_UPLOAD : {marlin_custom_firmware_upload}') + print('---- Marlin -----------------------------------') + print(f' PIOENV : {marlin_pioenv}') + print(f' SHORT_BUILD_VERSION : {marlin_short_build_version}') + print(f' STRING_CONFIG_H_AUTHOR : {marlin_string_config_h_author}') + print(f' MOTHERBOARD : {marlin_motherboard}') + print(f' BOARD_INFO_NAME : {marlin_board_info_name}') + print(f' CUSTOM_BUILD_FLAGS : {marlin_board_custom_build_flags}') + print(f' FIRMWARE_BIN : {marlin_firmware_bin}') + print(f' LONG_FILENAME_HOST_SUPPORT : {marlin_long_filename_host_support}') + print(f' LONG_FILENAME_WRITE_SUPPORT : {marlin_longname_write}') + print(f' CUSTOM_FIRMWARE_UPLOAD : {marlin_custom_firmware_upload}') print('---- Upload parameters ------------------------') - print(f' Source : {upload_firmware_source_name}') - print(f' Target : {upload_firmware_target_name}') - print(f' Port : {upload_port} @ {upload_speed} baudrate') - print(f' Timeout : {upload_timeout}') - print(f' Block size : {upload_blocksize}') - print(f' Compression : {upload_compression}') - print(f' Error ratio : {upload_error_ratio}') - print(f' Test : {upload_test}') - print(f' Reset : {upload_reset}') + print(f' Source : {upload_firmware_source_name}') + print(f' Target : {upload_firmware_target_name}') + print(f' Port : {upload_port} @ {upload_speed} baudrate') + print(f' Timeout : {upload_timeout}') + print(f' Block size : {upload_blocksize}') + print(f' Compression : {upload_compression}') + print(f' Error ratio : {upload_error_ratio}') + print(f' Test : {upload_test}') + print(f' Reset : {upload_reset}') print('-----------------------------------------------') # Custom implementations based on board parameters From e592dce46981a14d1cede8815179e1798c4f9ea3 Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Mon, 17 Jan 2022 03:28:52 -0600 Subject: [PATCH 09/17] not needed --- Marlin/src/sd/SdBaseFile.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Marlin/src/sd/SdBaseFile.h b/Marlin/src/sd/SdBaseFile.h index e953deb6af10..bda44c6bd5c3 100644 --- a/Marlin/src/sd/SdBaseFile.h +++ b/Marlin/src/sd/SdBaseFile.h @@ -30,8 +30,6 @@ * This file is part of the Arduino Sd2Card Library */ -#include "../inc/MarlinConfig.h" - #include "SdFatConfig.h" #include "SdVolume.h" From 671b574c9056be8850530cc790251d7aea3a9b7e Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Mon, 17 Jan 2022 23:18:59 +0100 Subject: [PATCH 10/17] Fix SFN generation --- Marlin/src/sd/SdBaseFile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marlin/src/sd/SdBaseFile.cpp b/Marlin/src/sd/SdBaseFile.cpp index cfbc8fe31354..64d0ad68bd80 100644 --- a/Marlin/src/sd/SdBaseFile.cpp +++ b/Marlin/src/sd/SdBaseFile.cpp @@ -1113,8 +1113,8 @@ bool SdBaseFile::openNext(SdBaseFile *dirFile, uint8_t oflag) { // Check minimal length if (iFree < 3) { // Append 4 extra characters - name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(1,9) + 'A'; - name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(1,9) + 'A'; + name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(0,24) + 'A'; + name[iFree++] = random(0,24) + 'A'; name[iFree++] = random(0,24) + 'A'; } // Append '~cnt' characters if (iFree > 5) iFree = 5; // Force the append in the last 3 characters of name part From 079735e9d59bb489452b3d070f931f5c1d48c410 Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Mon, 17 Jan 2022 23:20:26 +0100 Subject: [PATCH 11/17] Added LONG_FILENAME_WRITE and CUSTOM_FIRMWARE_UPLOAD to M115 --- Marlin/src/gcode/host/M115.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp index 08943ed5f299..145179a32cce 100644 --- a/Marlin/src/gcode/host/M115.cpp +++ b/Marlin/src/gcode/host/M115.cpp @@ -154,6 +154,12 @@ void GcodeSuite::M115() { // LONG_FILENAME_HOST_SUPPORT (M33) cap_line(F("LONG_FILENAME"), ENABLED(LONG_FILENAME_HOST_SUPPORT)); + // LONG_FILENAME_WRITE_SUPPORT (M20, M23, M28, M29, M30, M33, ...) + cap_line(F("LONG_FILENAME_WRITE"), ENABLED(LONG_FILENAME_WRITE_SUPPORT)); + + // CUSTOM_FIRMWARE_UPLOAD (M20) + cap_line(F("CUSTOM_FIRMWARE_UPLOAD"), ENABLED(CUSTOM_FIRMWARE_UPLOAD)); + // EXTENDED_M20 (M20 L) cap_line(F("EXTENDED_M20"), ENABLED(LONG_FILENAME_HOST_SUPPORT)); From 58ed6370c426303c9006ee0789014e41f2bfc534 Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Mon, 17 Jan 2022 23:21:15 +0100 Subject: [PATCH 12/17] More info on comment --- Marlin/Configuration_adv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 7e7e02d9afe8..9598755f5311 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1526,7 +1526,7 @@ // LCD's font must contain the characters. Check your selected LCD language. //#define UTF_FILENAME_SUPPORT - // This allows Marlin to create files with long filenames + // This allows Marlin to create/delete files with long filenames with M28, M30 and Binary Transfer Protocol //#define LONG_FILENAME_WRITE_SUPPORT // This allows hosts to request long names for files and folders with M33 From 5d9a9db3f6954c64d287904fcc561b33c1b92d08 Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Mon, 17 Jan 2022 23:29:18 +0100 Subject: [PATCH 13/17] Other comment --- Marlin/Configuration_adv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 9598755f5311..b2ecd14d0c78 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1529,7 +1529,7 @@ // This allows Marlin to create/delete files with long filenames with M28, M30 and Binary Transfer Protocol //#define LONG_FILENAME_WRITE_SUPPORT - // This allows hosts to request long names for files and folders with M33 + // This allows hosts to request long names for files and folders with M33 and M20 //#define LONG_FILENAME_HOST_SUPPORT // Enable this option to scroll long filenames in the SD card menu From 7bc2126fd2fdda694ac4444b7bef1d2fcbb93abe Mon Sep 17 00:00:00 2001 From: GHGiampy Date: Tue, 18 Jan 2022 00:42:47 +0100 Subject: [PATCH 14/17] Avoid writing LFN without reading --- Marlin/Configuration_adv.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index b2ecd14d0c78..a79252b164e6 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1526,11 +1526,12 @@ // LCD's font must contain the characters. Check your selected LCD language. //#define UTF_FILENAME_SUPPORT - // This allows Marlin to create/delete files with long filenames with M28, M30 and Binary Transfer Protocol - //#define LONG_FILENAME_WRITE_SUPPORT - // This allows hosts to request long names for files and folders with M33 and M20 //#define LONG_FILENAME_HOST_SUPPORT + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + // This allows Marlin to create/delete files with long filenames with M28, M30 and Binary Transfer Protocol + //#define LONG_FILENAME_WRITE_SUPPORT + #endif // Enable this option to scroll long filenames in the SD card menu //#define SCROLL_LONG_FILENAMES From 63f3b5a756248b62dd72a6a395a301cdb4ed4f2f Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Tue, 18 Jan 2022 00:12:21 -0600 Subject: [PATCH 15/17] tweak descriptions --- Marlin/Configuration_adv.h | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index a79252b164e6..ff76d837058b 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1526,37 +1526,23 @@ // LCD's font must contain the characters. Check your selected LCD language. //#define UTF_FILENAME_SUPPORT - // This allows hosts to request long names for files and folders with M33 and M20 - //#define LONG_FILENAME_HOST_SUPPORT - #if ENABLED(LONG_FILENAME_HOST_SUPPORT) - // This allows Marlin to create/delete files with long filenames with M28, M30 and Binary Transfer Protocol - //#define LONG_FILENAME_WRITE_SUPPORT - #endif + //#define LONG_FILENAME_HOST_SUPPORT // Get the long filename of a file/folder with 'M33 ' and list long filenames with 'M20 L' + //#define LONG_FILENAME_WRITE_SUPPORT // Create / delete files with long filenames via M28, M30, and Binary Transfer Protocol - // Enable this option to scroll long filenames in the SD card menu - //#define SCROLL_LONG_FILENAMES + //#define SCROLL_LONG_FILENAMES // Scroll long filenames in the SD card menu - // Leave the heaters on after Stop Print (not recommended!) - //#define SD_ABORT_NO_COOLDOWN + //#define SD_ABORT_NO_COOLDOWN // Leave the heaters on after Stop Print (not recommended!) /** - * This option allows you to abort SD printing when any endstop is triggered. - * This feature must be enabled with "M540 S1" or from the LCD menu. - * To have any effect, endstops must be enabled during SD printing. + * Abort SD printing when any endstop is triggered. + * This feature is enabled with 'M540 S1' or from the LCD menu. + * Endstops must be activated for this option to work. */ //#define SD_ABORT_ON_ENDSTOP_HIT - /** - * This option makes it easier to print the same SD Card file again. - * On print completion the LCD Menu will open with the file selected. - * You can just click to start the print, or navigate elsewhere. - */ - //#define SD_REPRINT_LAST_SELECTED_FILE + //#define SD_REPRINT_LAST_SELECTED_FILE // On print completion open the LCD Menu and select the same file - /** - * Auto-report SdCard status with M27 S - */ - //#define AUTO_REPORT_SD_STATUS + //#define AUTO_REPORT_SD_STATUS // Auto-report media status with 'M27 S' /** * Support for USB thumb drives using an Arduino USB Host Shield or From 49b620a33f0910d8f10e37a92f1519ca6e72ad1e Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Tue, 18 Jan 2022 00:18:51 -0600 Subject: [PATCH 16/17] shorter capability string --- Marlin/src/gcode/host/M115.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp index 145179a32cce..a4dca05cba6c 100644 --- a/Marlin/src/gcode/host/M115.cpp +++ b/Marlin/src/gcode/host/M115.cpp @@ -155,9 +155,9 @@ void GcodeSuite::M115() { cap_line(F("LONG_FILENAME"), ENABLED(LONG_FILENAME_HOST_SUPPORT)); // LONG_FILENAME_WRITE_SUPPORT (M20, M23, M28, M29, M30, M33, ...) - cap_line(F("LONG_FILENAME_WRITE"), ENABLED(LONG_FILENAME_WRITE_SUPPORT)); + cap_line(F("LFN_WRITE"), ENABLED(LONG_FILENAME_WRITE_SUPPORT)); - // CUSTOM_FIRMWARE_UPLOAD (M20) + // CUSTOM_FIRMWARE_UPLOAD (M20 F) cap_line(F("CUSTOM_FIRMWARE_UPLOAD"), ENABLED(CUSTOM_FIRMWARE_UPLOAD)); // EXTENDED_M20 (M20 L) From f0efe10f95ba4f8cde99af3233c5d08fa2524765 Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Tue, 18 Jan 2022 00:40:34 -0600 Subject: [PATCH 17/17] etc. --- Marlin/src/gcode/eeprom/M500-M504.cpp | 2 ++ Marlin/src/gcode/host/M115.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Marlin/src/gcode/eeprom/M500-M504.cpp b/Marlin/src/gcode/eeprom/M500-M504.cpp index a1f295ebde06..412d0033558f 100644 --- a/Marlin/src/gcode/eeprom/M500-M504.cpp +++ b/Marlin/src/gcode/eeprom/M500-M504.cpp @@ -56,6 +56,8 @@ void GcodeSuite::M502() { /** * M503: print settings currently in memory * + * S : Include / exclude header comments in the output. (Default: S1) + * * With CONFIGURATION_EMBEDDING: * C : Save the full Marlin configuration to SD Card as "mc.zip" */ diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp index a4dca05cba6c..45e0061a5b84 100644 --- a/Marlin/src/gcode/host/M115.cpp +++ b/Marlin/src/gcode/host/M115.cpp @@ -154,7 +154,7 @@ void GcodeSuite::M115() { // LONG_FILENAME_HOST_SUPPORT (M33) cap_line(F("LONG_FILENAME"), ENABLED(LONG_FILENAME_HOST_SUPPORT)); - // LONG_FILENAME_WRITE_SUPPORT (M20, M23, M28, M29, M30, M33, ...) + // LONG_FILENAME_WRITE_SUPPORT (M23, M28, M30...) cap_line(F("LFN_WRITE"), ENABLED(LONG_FILENAME_WRITE_SUPPORT)); // CUSTOM_FIRMWARE_UPLOAD (M20 F) @@ -185,7 +185,7 @@ void GcodeSuite::M115() { cap_line(F("MEATPACK"), SERIAL_IMPL.has_feature(port, SerialFeature::MeatPack)); // CONFIG_EXPORT - cap_line(F("CONFIG_EXPORT"), ENABLED(CONFIG_EMBED_AND_SAVE_TO_SD)); + cap_line(F("CONFIG_EXPORT"), ENABLED(CONFIGURATION_EMBEDDING)); // Machine Geometry #if ENABLED(M115_GEOMETRY_REPORT)