/** * Copyright (c) 2011-2018 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. */ #include "FatFile.h" //------------------------------------------------------------------------------ // uint8_t FatFile::lfnChecksum(uint8_t* name) { uint8_t sum = 0; for (uint8_t i = 0; i < 11; i++) { sum = (((sum & 1) << 7) | ((sum & 0xfe) >> 1)) + name[i]; } return sum; } #if USE_LONG_FILE_NAMES //------------------------------------------------------------------------------ // Saves about 90 bytes of flash on 328 over tolower(). inline char lfnToLower(char c) { return 'A' <= c && c <= 'Z' ? c + 'a' - 'A' : c; } //------------------------------------------------------------------------------ // Daniel Bernstein University of Illinois at Chicago. // Original had + instead of ^ static uint16_t Bernstein(uint16_t hash, const char *str, size_t len) { for (size_t i = 0; i < len; i++) { // hash = hash * 33 ^ str[i]; hash = ((hash << 5) + hash) ^ str[i]; } return hash; } //------------------------------------------------------------------------------ /** * Fetch a 16-bit long file name character. * * \param[in] ldir Pointer to long file name directory entry. * \param[in] i Index of character. * \return The 16-bit character. */ static uint16_t lfnGetChar(ldir_t *ldir, uint8_t i) { if (i < LDIR_NAME1_DIM) { return ldir->name1[i]; } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM)) { return ldir->name2[i - LDIR_NAME1_DIM]; } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM + LDIR_NAME2_DIM)) { return ldir->name3[i - LDIR_NAME1_DIM - LDIR_NAME2_DIM]; } return 0; } //------------------------------------------------------------------------------ static bool lfnGetName(ldir_t *ldir, char* name, size_t n) { uint8_t i; size_t k = 13*((ldir->ord & 0X1F) - 1); for (i = 0; i < 13; i++) { uint16_t c = lfnGetChar(ldir, i); if (c == 0 || k >= n) { break; } name[k++] = c >= 0X7F ? '?' : c; } // Terminate with zero byte if name fits. if (k < n && (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY)) { name[k] = 0; } // Truncate if name is too long. name[n - 1] = 0; return true; } //------------------------------------------------------------------------------ inline bool lfnLegalChar(char c) { if (c == '/' || c == '\\' || c == '"' || c == '*' || c == ':' || c == '<' || c == '>' || c == '?' || c == '|') { return false; } return 0X1F < c && c < 0X7F; } //------------------------------------------------------------------------------ /** * 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 lfnPutChar(ldir_t *ldir, uint8_t i, uint16_t c) { if (i < LDIR_NAME1_DIM) { ldir->name1[i] = c; } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM)) { ldir->name2[i - LDIR_NAME1_DIM] = c; } else if (i < (LDIR_NAME1_DIM + LDIR_NAME2_DIM + LDIR_NAME2_DIM)) { ldir->name3[i - LDIR_NAME1_DIM - LDIR_NAME2_DIM] = c; } } //------------------------------------------------------------------------------ static void lfnPutName(ldir_t *ldir, const char* name, size_t n) { size_t k = 13*((ldir->ord & 0X1F) - 1); for (uint8_t i = 0; i < 13; i++, k++) { uint16_t c = k < n ? name[k] : k == n ? 0 : 0XFFFF; lfnPutChar(ldir, i, c); } } //============================================================================== bool FatFile::getName(char* name, size_t size) { FatFile dirFile; ldir_t* ldir; if (!isOpen() || size < 13) { DBG_FAIL_MACRO; goto fail; } if (!isLFN()) { return getSFN(name); } if (!dirFile.openCluster(this)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t ord = 1; ord <= m_lfnOrd; ord++) { if (!dirFile.seekSet(32UL*(m_dirIndex - ord))) { DBG_FAIL_MACRO; goto fail; } ldir = reinterpret_cast(dirFile.readDirCache()); if (!ldir) { DBG_FAIL_MACRO; goto fail; } if (ldir->attr != DIR_ATT_LONG_NAME) { DBG_FAIL_MACRO; goto fail; } if (ord != (ldir->ord & 0X1F)) { DBG_FAIL_MACRO; goto fail; } if (!lfnGetName(ldir, name, size)) { DBG_FAIL_MACRO; goto fail; } if (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) { return true; } } // Fall into fail. DBG_FAIL_MACRO; fail: name[0] = 0; return false; } //------------------------------------------------------------------------------ bool FatFile::openCluster(FatFile* file) { if (file->m_dirCluster == 0) { return openRoot(file->m_vol); } memset(this, 0, sizeof(FatFile)); m_attr = FILE_ATTR_SUBDIR; m_flags = F_READ; m_vol = file->m_vol; m_firstCluster = file->m_dirCluster; return true; } //------------------------------------------------------------------------------ bool FatFile::parsePathName(const char* path, fname_t* fname, const char** ptr) { char c; bool is83; uint8_t bit = DIR_NT_LC_BASE; uint8_t lc = 0; uint8_t uc = 0; uint8_t i = 0; uint8_t in = 7; int end; int len = 0; int si; int dot; // Skip leading spaces. while (*path == ' ') { path++; } fname->lfn = path; for (len = 0; ; len++) { c = path[len]; if (c == 0 || isDirSeparator(c)) { break; } if (!lfnLegalChar(c)) { return false; } } // Advance to next path component. for (end = len; path[end] == ' ' || isDirSeparator(path[end]); end++) {} *ptr = &path[end]; // Back over spaces and dots. while (len) { c = path[len - 1]; if (c != '.' && c != ' ') { break; } len--; } // Max length of LFN is 255. if (len > 255) { return false; } fname->len = len; // Blank file short name. for (uint8_t k = 0; k < 11; k++) { fname->sfn[k] = ' '; } // skip leading spaces and dots. for (si = 0; path[si] == '.' || path[si] == ' '; si++) {} // Not 8.3 if leading dot or space. is83 = !si; // find last dot. for (dot = len - 1; dot >= 0 && path[dot] != '.'; dot--) {} for (; si < len; si++) { c = path[si]; if (c == ' ' || (c == '.' && dot != si)) { is83 = false; continue; } if (!legal83Char(c) && si != dot) { is83 = false; c = '_'; } if (si == dot || i > in) { if (in == 10) { // Done - extension longer than three characters. is83 = false; break; } if (si != dot) { is83 = false; } // Break if no dot and base-name is longer than eight characters. if (si > dot) { break; } si = dot; in = 10; // Max index for full 8.3 name. i = 8; // Place for extension. bit = DIR_NT_LC_EXT; // bit for extension. } else { if ('a' <= c && c <= 'z') { c += 'A' - 'a'; lc |= bit; } else if ('A' <= c && c <= 'Z') { uc |= bit; } fname->sfn[i++] = c; if (i < 7) { fname->seqPos = i; } } } if (fname->sfn[0] == ' ') { return false; } 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; } //------------------------------------------------------------------------------ bool FatFile::open(FatFile* dirFile, fname_t* fname, oflag_t oflag) { bool fnameFound = false; uint8_t lfnOrd = 0; uint8_t freeNeed; uint8_t freeFound = 0; uint8_t ord = 0; uint8_t chksum = 0; uint16_t freeIndex = 0; uint16_t curIndex; dir_t* dir; ldir_t* ldir; size_t len = fname->len; if (!dirFile || !dirFile->isDir() || isOpen()) { DBG_FAIL_MACRO; goto fail; } // Number of directory entries needed. freeNeed = fname->flags & FNAME_FLAG_NEED_LFN ? 1 + (len + 12)/13 : 1; dirFile->rewind(); while (1) { curIndex = dirFile->m_curPosition/32; dir = dirFile->readDirCache(true); if (!dir) { if (dirFile->getError()) { DBG_FAIL_MACRO; goto fail; } // At EOF goto create; } if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == DIR_NAME_FREE) { if (freeFound == 0) { freeIndex = curIndex; } if (freeFound < freeNeed) { freeFound++; } if (dir->name[0] == DIR_NAME_FREE) { goto create; } } else { if (freeFound < freeNeed) { freeFound = 0; } } // skip empty slot or '.' or '..' if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { lfnOrd = 0; } else if (DIR_IS_LONG_NAME(dir)) { ldir_t *ldir = reinterpret_cast(dir); if (!lfnOrd) { if ((ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) == 0) { continue; } lfnOrd = ord = ldir->ord & 0X1F; chksum = ldir->chksum; } else if (ldir->ord != --ord || chksum != ldir->chksum) { lfnOrd = 0; continue; } size_t k = 13*(ord - 1); if (k >= len) { // Not found. lfnOrd = 0; continue; } for (uint8_t i = 0; i < 13; i++) { uint16_t u = lfnGetChar(ldir, i); if (k == len) { if (u != 0) { // Not found. lfnOrd = 0; } break; } if (u > 255 || lfnToLower(u) != lfnToLower(fname->lfn[k++])) { // Not found. lfnOrd = 0; break; } } } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { if (lfnOrd) { if (1 == ord && lfnChecksum(dir->name) == chksum) { 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_FAIL_MACRO; goto fail; } // If at EOF start in next cluster. 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++; } while (freeFound < freeNeed) { // Will fail if FAT16 root. if (!dirFile->addDirCluster()) { DBG_FAIL_MACRO; goto fail; } // Done if more than one block per cluster. Max freeNeed is 21. if (dirFile->m_vol->blocksPerCluster() > 1) { break; } freeFound += 16; } if (fnameFound) { if (!dirFile->lfnUniqueSfn(fname)) { goto fail; } } if (!dirFile->seekSet(32UL*freeIndex)) { DBG_FAIL_MACRO; goto fail; } lfnOrd = freeNeed - 1; for (uint8_t ord = lfnOrd ; ord ; ord--) { ldir = reinterpret_cast(dirFile->readDirCache()); if (!ldir) { DBG_FAIL_MACRO; goto fail; } dirFile->m_vol->cacheDirty(); ldir->ord = ord == lfnOrd ? LDIR_ORD_LAST_LONG_ENTRY | ord : ord; ldir->attr = DIR_ATT_LONG_NAME; ldir->type = 0; ldir->chksum = lfnChecksum(fname->sfn); ldir->mustBeZero = 0; lfnPutName(ldir, fname->lfn, len); } curIndex = dirFile->m_curPosition/32; dir = dirFile->readDirCache(); if (!dir) { DBG_FAIL_MACRO; goto fail; } // initialize as empty file memset(dir, 0, sizeof(dir_t)); memcpy(dir->name, fname->sfn, 11); // Set base-name and extension lower case bits. dir->reservedNT = (DIR_NT_LC_BASE | DIR_NT_LC_EXT) & fname->flags; // set timestamps if (m_dateTime) { // call user date/time function m_dateTime(&dir->creationDate, &dir->creationTime); } else { // use default date/time dir->creationDate = FAT_DEFAULT_DATE; dir->creationTime = FAT_DEFAULT_TIME; } dir->lastAccessDate = dir->creationDate; dir->lastWriteDate = dir->creationDate; dir->lastWriteTime = dir->creationTime; // Force write of entry to device. dirFile->m_vol->cacheDirty(); open: // open entry in cache. if (!openCachedEntry(dirFile, curIndex, oflag, lfnOrd)) { DBG_FAIL_MACRO; goto fail; } return true; fail: return false; } //------------------------------------------------------------------------------ size_t FatFile::printName(print_t* pr) { FatFile dirFile; ldir_t* ldir; size_t n = 0; uint16_t u; uint8_t buf[13]; uint8_t i; if (!isLFN()) { return printSFN(pr); } if (!dirFile.openCluster(this)) { DBG_FAIL_MACRO; goto fail; } for (uint8_t ord = 1; ord <= m_lfnOrd; ord++) { if (!dirFile.seekSet(32UL*(m_dirIndex - ord))) { DBG_FAIL_MACRO; goto fail; } ldir = reinterpret_cast(dirFile.readDirCache()); if (!ldir) { DBG_FAIL_MACRO; goto fail; } if (ldir->attr != DIR_ATT_LONG_NAME || ord != (ldir->ord & 0X1F)) { DBG_FAIL_MACRO; goto fail; } for (i = 0; i < 13; i++) { u = lfnGetChar(ldir, i); if (u == 0) { // End of name. break; } buf[i] = u < 0X7F ? u : '?'; n++; } pr->write(buf, i); } return n; fail: return 0; } //------------------------------------------------------------------------------ bool FatFile::remove() { bool last; uint8_t chksum; uint8_t ord; FatFile dirFile; dir_t* dir; ldir_t* ldir; // Cant' remove not open for write. if (!isFile() || !(m_flags & F_WRITE)) { 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(FatCache::CACHE_FOR_WRITE); if (!dir) { DBG_FAIL_MACRO; goto fail; } chksum = lfnChecksum(dir->name); // Mark entry deleted. dir->name[0] = DIR_NAME_DELETED; // Set this file closed. m_attr = FILE_ATTR_CLOSED; // 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 (ord = 1; ord <= m_lfnOrd; ord++) { if (!dirFile.seekSet(32UL*(m_dirIndex - ord))) { DBG_FAIL_MACRO; goto fail; } ldir = reinterpret_cast(dirFile.readDirCache()); if (!ldir) { DBG_FAIL_MACRO; goto fail; } if (ldir->attr != DIR_ATT_LONG_NAME || ord != (ldir->ord & 0X1F) || chksum != ldir->chksum) { DBG_FAIL_MACRO; goto fail; } last = ldir->ord & LDIR_ORD_LAST_LONG_ENTRY; ldir->ord = DIR_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; } //------------------------------------------------------------------------------ bool FatFile::lfnUniqueSfn(fname_t* fname) { const uint8_t FIRST_HASH_SEQ = 2; // min value is 2 uint8_t pos = fname->seqPos;; dir_t *dir; uint16_t hex; DBG_HALT_IF(!(fname->flags & FNAME_FLAG_LOST_CHARS)); DBG_HALT_IF(fname->sfn[pos] != '~' && fname->sfn[pos + 1] != '1'); for (uint8_t seq = 2; seq < 100; seq++) { if (seq < FIRST_HASH_SEQ) { fname->sfn[pos + 1] = '0' + seq; } else { DBG_PRINT_IF(seq > FIRST_HASH_SEQ); hex = Bernstein(seq + fname->len, fname->lfn, fname->len); 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] == DIR_NAME_FREE) { goto done; } if (DIR_IS_FILE_OR_SUBDIR(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; } #endif // #if USE_LONG_FILE_NAMES