serial_debugger/hardware/sd_card_formatter/src/SdFat/FatLib/FatFileLFN.cpp

686 lines
17 KiB
C++

/**
* 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<ldir_t*>(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<ldir_t*>(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<ldir_t*>(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<ldir_t*>(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<ldir_t*>(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