/** * Copyright (c) 2011-2022 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #define DBG_FILE "FatFileLFN.cpp" #include "../common/DebugMacros.h" #include "../common/FsUtf.h" #include "../common/upcase.h" #include "FatLib.h" #if USE_LONG_FILE_NAMES //------------------------------------------------------------------------------ static bool isLower(char c) { return 'a' <= c && c <= 'z'; } //------------------------------------------------------------------------------ static bool isUpper(char c) { return 'A' <= c && c <= 'Z'; } //------------------------------------------------------------------------------ // A bit smaller than toupper in AVR 328. inline char toUpper(char c) { return isLower(c) ? c - 'a' + 'A' : c; } //------------------------------------------------------------------------------ /** * Store a 16-bit long file name character. * * \param[in] ldir Pointer to long file name directory entry. * \param[in] i Index of character. * \param[in] c The 16-bit character. */ static void putLfnChar(DirLfn_t* ldir, uint8_t i, uint16_t c) { if (i < 5) { setLe16(ldir->unicode1 + 2 * i, c); } else if (i < 11) { setLe16(ldir->unicode2 + 2 * (i - 5), c); } else if (i < 13) { setLe16(ldir->unicode3 + 2 * (i - 11), c); } } //============================================================================== bool FatFile::cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) { FatFile dir = *this; DirLfn_t* ldir; fname->reset(); for (uint8_t order = 1; order <= lfnOrd; order++) { ldir = reinterpret_cast(dir.cacheDir(index - order)); if (!ldir) { DBG_FAIL_MACRO; goto fail; } // These should be checked in caller. DBG_HALT_IF(ldir->attributes != FAT_ATTRIB_LONG_NAME); DBG_HALT_IF(order != (ldir->order & 0X1F)); for (uint8_t i = 0; i < 13; i++) { uint16_t u = getLfnChar(ldir, i); if (fname->atEnd()) { return u == 0; } #if USE_UTF8_LONG_NAMES uint16_t cp = fname->get16(); // Make sure caller checked for valid UTF-8. DBG_HALT_IF(cp == 0XFFFF); if (toUpcase(u) != toUpcase(cp)) { return false; } #else // USE_UTF8_LONG_NAMES if (u > 0X7F || toUpper(u) != toUpper(fname->getch())) { return false; } #endif // USE_UTF8_LONG_NAMES } } return true; fail: return false; } //------------------------------------------------------------------------------ bool FatFile::createLFN(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) { FatFile dir = *this; DirLfn_t* ldir; uint8_t checksum = lfnChecksum(fname->sfn); uint8_t fc = 0; fname->reset(); for (uint8_t order = 1; order <= lfnOrd; order++) { ldir = reinterpret_cast(dir.cacheDir(index - order)); if (!ldir) { DBG_FAIL_MACRO; goto fail; } dir.m_vol->cacheDirty(); ldir->order = order == lfnOrd ? FAT_ORDER_LAST_LONG_ENTRY | order : order; ldir->attributes = FAT_ATTRIB_LONG_NAME; ldir->mustBeZero1 = 0; ldir->checksum = checksum; setLe16(ldir->mustBeZero2, 0); for (uint8_t i = 0; i < 13; i++) { uint16_t cp; if (fname->atEnd()) { cp = fc++ ? 0XFFFF : 0; } else { cp = fname->get16(); // Verify caller checked for valid UTF-8. DBG_HALT_IF(cp == 0XFFFF); } putLfnChar(ldir, i, cp); } } return true; fail: return false; } //------------------------------------------------------------------------------ bool FatFile::makeSFN(FatLfn_t* fname) { bool is83; // char c; uint8_t c; uint8_t bit = FAT_CASE_LC_BASE; uint8_t lc = 0; uint8_t uc = 0; uint8_t i = 0; uint8_t in = 7; const char* dot; const char* end = fname->end; const char* ptr = fname->begin; // Assume not zero length. DBG_HALT_IF(end == ptr); // Assume blanks removed from start and end. DBG_HALT_IF(*ptr == ' ' || *(end - 1) == ' ' || *(end - 1) == '.'); // Blank file short name. for (uint8_t k = 0; k < 11; k++) { fname->sfn[k] = ' '; } // Not 8.3 if starts with dot. is83 = *ptr == '.' ? false : true; // Skip leading dots. for (; *ptr == '.'; ptr++) { } // Find last dot. for (dot = end - 1; dot > ptr && *dot != '.'; dot--) { } for (; ptr < end; ptr++) { c = *ptr; if (c == '.' && ptr == dot) { in = 10; // Max index for full 8.3 name. i = 8; // Place for extension. bit = FAT_CASE_LC_EXT; // bit for extension. } else { if (sfnReservedChar(c)) { is83 = false; // Skip UTF-8 trailing characters. if ((c & 0XC0) == 0X80) { continue; } c = '_'; } if (i > in) { is83 = false; if (in == 10 || ptr > dot) { // Done - extension longer than three characters or no extension. break; } // Skip to dot. ptr = dot - 1; continue; } if (isLower(c)) { c += 'A' - 'a'; lc |= bit; } else if (isUpper(c)) { uc |= bit; } fname->sfn[i++] = c; if (i < 7) { fname->seqPos = i; } } } if (fname->sfn[0] == ' ') { DBG_HALT_MACRO; goto fail; } if (is83) { fname->flags = (lc & uc) ? FNAME_FLAG_MIXED_CASE : lc; } else { fname->flags = FNAME_FLAG_LOST_CHARS; fname->sfn[fname->seqPos] = '~'; fname->sfn[fname->seqPos + 1] = '1'; } return true; fail: return false; } //------------------------------------------------------------------------------ bool FatFile::makeUniqueSfn(FatLfn_t* fname) { const uint8_t FIRST_HASH_SEQ = 2; // min value is 2 uint8_t pos = fname->seqPos; DirFat_t* dir; uint16_t hex = 0; DBG_HALT_IF(!(fname->flags & FNAME_FLAG_LOST_CHARS)); DBG_HALT_IF(fname->sfn[pos] != '~' && fname->sfn[pos + 1] != '1'); for (uint8_t seq = FIRST_HASH_SEQ; seq < 100; seq++) { DBG_WARN_IF(seq > FIRST_HASH_SEQ); hex += millis(); if (pos > 3) { // Make space in name for ~HHHH. pos = 3; } for (uint8_t i = pos + 4; i > pos; i--) { uint8_t h = hex & 0XF; fname->sfn[i] = h < 10 ? h + '0' : h + 'A' - 10; hex >>= 4; } fname->sfn[pos] = '~'; rewind(); while (1) { dir = readDirCache(true); if (!dir) { if (!getError()) { // At EOF and name not found if no error. goto done; } DBG_FAIL_MACRO; goto fail; } if (dir->name[0] == FAT_NAME_FREE) { goto done; } if (isFatFileOrSubdir(dir) && !memcmp(fname->sfn, dir->name, 11)) { // Name found - try another. break; } } } // fall inti fail - too many tries. DBG_FAIL_MACRO; fail: return false; done: return true; } //------------------------------------------------------------------------------ bool FatFile::open(FatFile* dirFile, FatLfn_t* fname, oflag_t oflag) { bool fnameFound = false; uint8_t lfnOrd = 0; uint8_t freeFound = 0; uint8_t freeNeed; uint8_t order = 0; uint8_t checksum = 0; uint8_t ms10; uint8_t nameOrd; uint16_t curIndex; uint16_t date; uint16_t freeIndex = 0; uint16_t freeTotal; uint16_t time; DirFat_t* dir; DirLfn_t* ldir; auto vol = dirFile->m_vol; if (!dirFile->isDir() || isOpen()) { DBG_FAIL_MACRO; goto fail; } // Number of directory entries needed. nameOrd = (fname->len + 12) / 13; freeNeed = (fname->flags & FNAME_FLAG_NEED_LFN) ? 1 + nameOrd : 1; dirFile->rewind(); while (1) { curIndex = dirFile->m_curPosition / FS_DIR_SIZE; dir = dirFile->readDirCache(); if (!dir) { if (dirFile->getError()) { DBG_FAIL_MACRO; goto fail; } // At EOF goto create; } if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == FAT_NAME_FREE) { if (freeFound == 0) { freeIndex = curIndex; } if (freeFound < freeNeed) { freeFound++; } if (dir->name[0] == FAT_NAME_FREE) { goto create; } } else { if (freeFound < freeNeed) { freeFound = 0; } } // skip empty slot or '.' or '..' if (dir->name[0] == FAT_NAME_DELETED || dir->name[0] == '.') { lfnOrd = 0; } else if (isFatLongName(dir)) { ldir = reinterpret_cast(dir); if (!lfnOrd) { order = ldir->order & 0X1F; if (order != nameOrd || (ldir->order & FAT_ORDER_LAST_LONG_ENTRY) == 0) { continue; } lfnOrd = nameOrd; checksum = ldir->checksum; } else if (ldir->order != --order || checksum != ldir->checksum) { lfnOrd = 0; continue; } if (order == 1) { if (!dirFile->cmpName(curIndex + 1, fname, lfnOrd)) { lfnOrd = 0; } } } else if (isFatFileOrSubdir(dir)) { if (lfnOrd) { if (1 == order && lfnChecksum(dir->name) == checksum) { goto found; } DBG_FAIL_MACRO; goto fail; } if (!memcmp(dir->name, fname->sfn, sizeof(fname->sfn))) { if (!(fname->flags & FNAME_FLAG_LOST_CHARS)) { goto found; } fnameFound = true; } } else { lfnOrd = 0; } } found: // Don't open if create only. if (oflag & O_EXCL) { DBG_FAIL_MACRO; goto fail; } goto open; create: // don't create unless O_CREAT and write mode if (!(oflag & O_CREAT) || !isWriteMode(oflag)) { DBG_WARN_MACRO; goto fail; } // Keep found entries or start at current index if no free entries found. if (freeFound == 0) { freeIndex = curIndex; } while (freeFound < freeNeed) { dir = dirFile->readDirCache(); if (!dir) { if (dirFile->getError()) { DBG_FAIL_MACRO; goto fail; } // EOF if no error. break; } freeFound++; } // Loop handles the case of huge filename and cluster size one. freeTotal = freeFound; while (freeTotal < freeNeed) { // Will fail if FAT16 root. if (!dirFile->addDirCluster()) { DBG_FAIL_MACRO; goto fail; } // 16-bit freeTotal needed for large cluster size. freeTotal += vol->dirEntriesPerCluster(); } if (fnameFound) { if (!dirFile->makeUniqueSfn(fname)) { goto fail; } } lfnOrd = freeNeed - 1; curIndex = freeIndex + lfnOrd; if (!dirFile->createLFN(curIndex, fname, lfnOrd)) { goto fail; } dir = dirFile->cacheDir(curIndex); if (!dir) { DBG_FAIL_MACRO; goto fail; } // initialize as empty file memset(dir, 0, sizeof(DirFat_t)); memcpy(dir->name, fname->sfn, 11); // Set base-name and extension lower case bits. dir->caseFlags = (FAT_CASE_LC_BASE | FAT_CASE_LC_EXT) & fname->flags; // Set timestamps. if (FsDateTime::callback) { // call user date/time function FsDateTime::callback(&date, &time, &ms10); setLe16(dir->createDate, date); setLe16(dir->createTime, time); dir->createTimeMs = ms10; } else { setLe16(dir->createDate, FS_DEFAULT_DATE); setLe16(dir->modifyDate, FS_DEFAULT_DATE); setLe16(dir->accessDate, FS_DEFAULT_DATE); if (FS_DEFAULT_TIME) { setLe16(dir->createTime, FS_DEFAULT_TIME); setLe16(dir->modifyTime, FS_DEFAULT_TIME); } } // Force write of entry to device. vol->cacheDirty(); open: // open entry in cache. if (!openCachedEntry(dirFile, curIndex, oflag, lfnOrd)) { DBG_FAIL_MACRO; goto fail; } return true; fail: return false; } //------------------------------------------------------------------------------ bool FatFile::parsePathName(const char* path, FatLfn_t* fname, const char** ptr) { size_t len = 0; // Skip leading spaces. while (*path == ' ') { path++; } fname->begin = path; fname->len = 0; while (*path && !isDirSeparator(*path)) { #if USE_UTF8_LONG_NAMES uint32_t cp; // Allow end = path + 4 since path is zero terminated. path = FsUtf::mbToCp(path, path + 4, &cp); if (!path) { DBG_FAIL_MACRO; goto fail; } len += cp <= 0XFFFF ? 1 : 2; if (cp < 0X80 && lfnReservedChar(cp)) { DBG_FAIL_MACRO; goto fail; } #else // USE_UTF8_LONG_NAMES uint8_t cp = *path++; if (cp >= 0X80 || lfnReservedChar(cp)) { DBG_FAIL_MACRO; goto fail; } len++; #endif // USE_UTF8_LONG_NAMES if (cp != '.' && cp != ' ') { // Need to trim trailing dots spaces. fname->len = len; fname->end = path; } } if (!fname->len || fname->len > FAT_MAX_LFN_LENGTH) { DBG_FAIL_MACRO; goto fail; } // Advance to next path component. for (; *path == ' ' || isDirSeparator(*path); path++) { } *ptr = path; return makeSFN(fname); fail: return false; } //------------------------------------------------------------------------------ bool FatFile::remove() { bool last; uint8_t checksum; FatFile dirFile; DirFat_t* dir; DirLfn_t* ldir; // Cant' remove not open for write. if (!isWritable()) { DBG_FAIL_MACRO; goto fail; } // Free any clusters. if (m_firstCluster && !m_vol->freeChain(m_firstCluster)) { DBG_FAIL_MACRO; goto fail; } // Cache directory entry. dir = cacheDirEntry(FsCache::CACHE_FOR_WRITE); if (!dir) { DBG_FAIL_MACRO; goto fail; } checksum = lfnChecksum(dir->name); // Mark entry deleted. dir->name[0] = FAT_NAME_DELETED; // Set this file closed. m_attributes = FILE_ATTR_CLOSED; m_flags = 0; // Write entry to device. if (!m_vol->cacheSync()) { DBG_FAIL_MACRO; goto fail; } if (!isLFN()) { // Done, no LFN entries. return true; } if (!dirFile.openCluster(this)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t order = 1; order <= m_lfnOrd; order++) { ldir = reinterpret_cast(dirFile.cacheDir(m_dirIndex - order)); if (!ldir) { DBG_FAIL_MACRO; goto fail; } if (ldir->attributes != FAT_ATTRIB_LONG_NAME || order != (ldir->order & 0X1F) || checksum != ldir->checksum) { DBG_FAIL_MACRO; goto fail; } last = ldir->order & FAT_ORDER_LAST_LONG_ENTRY; ldir->order = FAT_NAME_DELETED; m_vol->cacheDirty(); if (last) { if (!m_vol->cacheSync()) { DBG_FAIL_MACRO; goto fail; } return true; } } // Fall into fail. DBG_FAIL_MACRO; fail: return false; } #endif // #if USE_LONG_FILE_NAMES