735 lines
20 KiB
C++
735 lines
20 KiB
C++
/**
|
|
* 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 "ExFatFile.cpp"
|
|
#include "../common/DebugMacros.h"
|
|
#include "../common/FsUtf.h"
|
|
#include "ExFatLib.h"
|
|
//------------------------------------------------------------------------------
|
|
/** test for legal character.
|
|
*
|
|
* \param[in] c character to be tested.
|
|
*
|
|
* \return true for legal character else false.
|
|
*/
|
|
inline bool lfnLegalChar(uint8_t c) {
|
|
#if USE_UTF8_LONG_NAMES
|
|
return !lfnReservedChar(c);
|
|
#else // USE_UTF8_LONG_NAMES
|
|
return !(lfnReservedChar(c) || c & 0X80);
|
|
#endif // USE_UTF8_LONG_NAMES
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::attrib(uint8_t bits) {
|
|
if (!isFileOrSubDir() || (bits & FS_ATTRIB_USER_SETTABLE) != bits) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// Don't allow read-only to be set if the file is open for write.
|
|
if ((bits & FS_ATTRIB_READ_ONLY) && isWritable()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
m_attributes = (m_attributes & ~FS_ATTRIB_USER_SETTABLE) | bits;
|
|
// insure sync() will update dir entry
|
|
m_flags |= FILE_FLAG_DIR_DIRTY;
|
|
if (!sync()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
uint8_t* ExFatFile::dirCache(uint8_t set, uint8_t options) {
|
|
DirPos_t pos = m_dirPos;
|
|
if (m_vol->dirSeek(&pos, FS_DIR_SIZE * set) != 1) {
|
|
return nullptr;
|
|
}
|
|
return m_vol->dirCache(&pos, options);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::close() {
|
|
bool rtn = sync();
|
|
m_attributes = FILE_ATTR_CLOSED;
|
|
m_flags = 0;
|
|
return rtn;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::contiguousRange(uint32_t* bgnSector, uint32_t* endSector) {
|
|
if (!isContiguous()) {
|
|
return false;
|
|
}
|
|
if (bgnSector) {
|
|
*bgnSector = firstSector();
|
|
}
|
|
if (endSector) {
|
|
*endSector =
|
|
firstSector() + ((m_validLength - 1) >> m_vol->bytesPerSectorShift());
|
|
}
|
|
return true;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void ExFatFile::fgetpos(fspos_t* pos) const {
|
|
pos->position = m_curPosition;
|
|
pos->cluster = m_curCluster;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int ExFatFile::fgets(char* str, int num, char* delim) {
|
|
char ch;
|
|
int n = 0;
|
|
int r = -1;
|
|
while ((n + 1) < num && (r = read(&ch, 1)) == 1) {
|
|
// delete CR
|
|
if (ch == '\r') {
|
|
continue;
|
|
}
|
|
str[n++] = ch;
|
|
if (!delim) {
|
|
if (ch == '\n') {
|
|
break;
|
|
}
|
|
} else {
|
|
if (strchr(delim, ch)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (r < 0) {
|
|
// read error
|
|
return -1;
|
|
}
|
|
str[n] = '\0';
|
|
return n;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
uint32_t ExFatFile::firstSector() const {
|
|
return m_firstCluster ? m_vol->clusterStartSector(m_firstCluster) : 0;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
void ExFatFile::fsetpos(const fspos_t* pos) {
|
|
m_curPosition = pos->position;
|
|
m_curCluster = pos->cluster;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::getAccessDateTime(uint16_t* pdate, uint16_t* ptime) {
|
|
DirFile_t* df = reinterpret_cast<DirFile_t*>(
|
|
m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ));
|
|
if (!df) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
*pdate = getLe16(df->accessDate);
|
|
*ptime = getLe16(df->accessTime);
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::getCreateDateTime(uint16_t* pdate, uint16_t* ptime) {
|
|
DirFile_t* df = reinterpret_cast<DirFile_t*>(
|
|
m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ));
|
|
if (!df) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
*pdate = getLe16(df->createDate);
|
|
*ptime = getLe16(df->createTime);
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::getModifyDateTime(uint16_t* pdate, uint16_t* ptime) {
|
|
DirFile_t* df = reinterpret_cast<DirFile_t*>(
|
|
m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ));
|
|
if (!df) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
*pdate = getLe16(df->modifyDate);
|
|
*ptime = getLe16(df->modifyTime);
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::isBusy() { return m_vol->isBusy(); }
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::open(const char* path, oflag_t oflag) {
|
|
return open(ExFatVolume::cwv(), path, oflag);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::open(ExFatVolume* vol, const char* path, oflag_t oflag) {
|
|
return vol && open(vol->vwd(), path, oflag);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::open(ExFatFile* dirFile, const char* path, oflag_t oflag) {
|
|
ExFatFile tmpDir;
|
|
ExName_t fname;
|
|
// error if already open
|
|
if (isOpen() || !dirFile->isDir()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (isDirSeparator(*path)) {
|
|
while (isDirSeparator(*path)) {
|
|
path++;
|
|
}
|
|
if (*path == 0) {
|
|
return openRoot(dirFile->m_vol);
|
|
}
|
|
if (!tmpDir.openRoot(dirFile->m_vol)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
dirFile = &tmpDir;
|
|
}
|
|
while (1) {
|
|
if (!parsePathName(path, &fname, &path)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (*path == 0) {
|
|
break;
|
|
}
|
|
if (!openPrivate(dirFile, &fname, O_RDONLY)) {
|
|
DBG_WARN_MACRO;
|
|
goto fail;
|
|
}
|
|
tmpDir = *this;
|
|
dirFile = &tmpDir;
|
|
close();
|
|
}
|
|
return openPrivate(dirFile, &fname, oflag);
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::open(uint32_t index, oflag_t oflag) {
|
|
ExFatVolume* vol = ExFatVolume::cwv();
|
|
return vol ? open(vol->vwd(), index, oflag) : false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::open(ExFatFile* dirFile, uint32_t index, oflag_t oflag) {
|
|
if (dirFile->seekSet(FS_DIR_SIZE * index) && openNext(dirFile, oflag)) {
|
|
if (dirIndex() == index) {
|
|
return true;
|
|
}
|
|
close();
|
|
DBG_FAIL_MACRO;
|
|
}
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::openCwd() {
|
|
if (isOpen() || !ExFatVolume::cwv()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
*this = *ExFatVolume::cwv()->vwd();
|
|
rewind();
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::openNext(ExFatFile* dir, oflag_t oflag) {
|
|
if (isOpen() || !dir->isDir() || (dir->curPosition() & 0X1F)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
return openPrivate(dir, nullptr, oflag);
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::openPrivate(ExFatFile* dir, ExName_t* fname, oflag_t oflag) {
|
|
int n;
|
|
uint8_t modeFlags;
|
|
uint8_t* cache __attribute__((unused));
|
|
DirPos_t freePos __attribute__((unused));
|
|
DirFile_t* dirFile;
|
|
DirStream_t* dirStream;
|
|
DirName_t* dirName;
|
|
uint8_t buf[FS_DIR_SIZE];
|
|
uint8_t freeCount = 0;
|
|
uint8_t freeNeed = 3;
|
|
bool inSet = false;
|
|
|
|
// error if already open, no access mode, or no directory.
|
|
if (isOpen() || !dir->isDir()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
|
|
switch (oflag & O_ACCMODE) {
|
|
case O_RDONLY:
|
|
modeFlags = FILE_FLAG_READ;
|
|
break;
|
|
case O_WRONLY:
|
|
modeFlags = FILE_FLAG_WRITE;
|
|
break;
|
|
case O_RDWR:
|
|
modeFlags = FILE_FLAG_READ | FILE_FLAG_WRITE;
|
|
break;
|
|
default:
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
modeFlags |= oflag & O_APPEND ? FILE_FLAG_APPEND : 0;
|
|
|
|
if (fname) {
|
|
freeNeed = 2 + (fname->nameLength + 14) / 15;
|
|
dir->rewind();
|
|
}
|
|
|
|
while (1) {
|
|
n = dir->read(buf, FS_DIR_SIZE);
|
|
if (n == 0) {
|
|
goto create;
|
|
}
|
|
if (n != FS_DIR_SIZE) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (!(buf[0] & EXFAT_TYPE_USED)) {
|
|
// Unused entry.
|
|
if (freeCount == 0) {
|
|
freePos.position = dir->curPosition() - FS_DIR_SIZE;
|
|
freePos.cluster = dir->curCluster();
|
|
}
|
|
if (freeCount < freeNeed) {
|
|
freeCount++;
|
|
}
|
|
if (buf[0] == EXFAT_TYPE_END_DIR) {
|
|
if (fname) {
|
|
goto create;
|
|
}
|
|
// Likely openNext call.
|
|
DBG_WARN_MACRO;
|
|
goto fail;
|
|
}
|
|
inSet = false;
|
|
} else if (!inSet) {
|
|
if (freeCount < freeNeed) {
|
|
freeCount = 0;
|
|
}
|
|
if (buf[0] != EXFAT_TYPE_FILE) {
|
|
continue;
|
|
}
|
|
inSet = true;
|
|
memset(this, 0, sizeof(ExFatFile));
|
|
dirFile = reinterpret_cast<DirFile_t*>(buf);
|
|
m_setCount = dirFile->setCount;
|
|
m_attributes = getLe16(dirFile->attributes) & FS_ATTRIB_COPY;
|
|
if (!(m_attributes & FS_ATTRIB_DIRECTORY)) {
|
|
m_attributes |= FILE_ATTR_FILE;
|
|
}
|
|
m_vol = dir->volume();
|
|
m_dirPos.cluster = dir->curCluster();
|
|
m_dirPos.position = dir->curPosition() - FS_DIR_SIZE;
|
|
m_dirPos.isContiguous = dir->isContiguous();
|
|
} else if (buf[0] == EXFAT_TYPE_STREAM) {
|
|
dirStream = reinterpret_cast<DirStream_t*>(buf);
|
|
m_flags = modeFlags;
|
|
if (dirStream->flags & EXFAT_FLAG_CONTIGUOUS) {
|
|
m_flags |= FILE_FLAG_CONTIGUOUS;
|
|
}
|
|
m_validLength = getLe64(dirStream->validLength);
|
|
m_firstCluster = getLe32(dirStream->firstCluster);
|
|
m_dataLength = getLe64(dirStream->dataLength);
|
|
if (!fname) {
|
|
goto found;
|
|
}
|
|
fname->reset();
|
|
if (fname->nameLength != dirStream->nameLength ||
|
|
fname->nameHash != getLe16(dirStream->nameHash)) {
|
|
inSet = false;
|
|
}
|
|
} else if (buf[0] == EXFAT_TYPE_NAME) {
|
|
dirName = reinterpret_cast<DirName_t*>(buf);
|
|
if (!cmpName(dirName, fname)) {
|
|
inSet = false;
|
|
continue;
|
|
}
|
|
if (fname->atEnd()) {
|
|
goto found;
|
|
}
|
|
} else {
|
|
inSet = false;
|
|
}
|
|
}
|
|
|
|
found:
|
|
// Don't open if create only.
|
|
if (oflag & O_EXCL) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// Write, truncate, or at end is an error for a directory or read-only file.
|
|
if ((oflag & (O_TRUNC | O_AT_END)) || (m_flags & FILE_FLAG_WRITE)) {
|
|
if (isSubDir() || isReadOnly() || EXFAT_READ_ONLY) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
#if !EXFAT_READ_ONLY
|
|
if (oflag & O_TRUNC) {
|
|
if (!(m_flags & FILE_FLAG_WRITE)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (!truncate(0)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
} else if ((oflag & O_AT_END) && !seekSet(fileSize())) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (isWritable()) {
|
|
m_attributes |= FS_ATTRIB_ARCHIVE;
|
|
}
|
|
#endif // !EXFAT_READ_ONLY
|
|
return true;
|
|
|
|
create:
|
|
#if EXFAT_READ_ONLY
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
#else // EXFAT_READ_ONLY
|
|
// don't create unless O_CREAT and write
|
|
if (!(oflag & O_CREAT) || !(modeFlags & FILE_FLAG_WRITE) || !fname) {
|
|
DBG_WARN_MACRO;
|
|
goto fail;
|
|
}
|
|
while (freeCount < freeNeed) {
|
|
n = dir->read(buf, FS_DIR_SIZE);
|
|
if (n == 0) {
|
|
uint32_t saveCurCluster = dir->m_curCluster;
|
|
if (!dir->addDirCluster()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
dir->m_curCluster = saveCurCluster;
|
|
continue;
|
|
}
|
|
if (n != FS_DIR_SIZE) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (freeCount == 0) {
|
|
freePos.position = dir->curPosition() - FS_DIR_SIZE;
|
|
freePos.cluster = dir->curCluster();
|
|
}
|
|
freeCount++;
|
|
}
|
|
freePos.isContiguous = dir->isContiguous();
|
|
memset(this, 0, sizeof(ExFatFile));
|
|
m_vol = dir->volume();
|
|
m_attributes = FILE_ATTR_FILE | FS_ATTRIB_ARCHIVE;
|
|
m_dirPos = freePos;
|
|
fname->reset();
|
|
for (uint8_t i = 0; i < freeNeed; i++) {
|
|
cache = dirCache(i, FsCache::CACHE_FOR_WRITE);
|
|
if (!cache || (cache[0] & 0x80)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
memset(cache, 0, FS_DIR_SIZE);
|
|
if (i == 0) {
|
|
dirFile = reinterpret_cast<DirFile_t*>(cache);
|
|
dirFile->type = EXFAT_TYPE_FILE;
|
|
m_setCount = freeNeed - 1;
|
|
dirFile->setCount = m_setCount;
|
|
|
|
if (FsDateTime::callback) {
|
|
uint16_t date, time;
|
|
uint8_t ms10;
|
|
FsDateTime::callback(&date, &time, &ms10);
|
|
setLe16(dirFile->createDate, date);
|
|
setLe16(dirFile->createTime, time);
|
|
dirFile->createTimeMs = ms10;
|
|
} else {
|
|
setLe16(dirFile->createDate, FS_DEFAULT_DATE);
|
|
setLe16(dirFile->modifyDate, FS_DEFAULT_DATE);
|
|
setLe16(dirFile->accessDate, FS_DEFAULT_DATE);
|
|
if (FS_DEFAULT_TIME) {
|
|
setLe16(dirFile->createTime, FS_DEFAULT_TIME);
|
|
setLe16(dirFile->modifyTime, FS_DEFAULT_TIME);
|
|
setLe16(dirFile->accessTime, FS_DEFAULT_TIME);
|
|
}
|
|
}
|
|
} else if (i == 1) {
|
|
dirStream = reinterpret_cast<DirStream_t*>(cache);
|
|
dirStream->type = EXFAT_TYPE_STREAM;
|
|
dirStream->flags = EXFAT_FLAG_ALWAYS1;
|
|
m_flags = modeFlags | FILE_FLAG_DIR_DIRTY;
|
|
dirStream->nameLength = fname->nameLength;
|
|
setLe16(dirStream->nameHash, fname->nameHash);
|
|
} else {
|
|
dirName = reinterpret_cast<DirName_t*>(cache);
|
|
dirName->type = EXFAT_TYPE_NAME;
|
|
for (size_t k = 0; k < 15; k++) {
|
|
if (fname->atEnd()) {
|
|
break;
|
|
}
|
|
uint16_t u = fname->get16();
|
|
setLe16(dirName->unicode + 2 * k, u);
|
|
}
|
|
}
|
|
}
|
|
return sync();
|
|
#endif // EXFAT_READ_ONLY
|
|
|
|
fail:
|
|
// close file
|
|
m_attributes = FILE_ATTR_CLOSED;
|
|
m_flags = 0;
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::openRoot(ExFatVolume* vol) {
|
|
if (isOpen()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
memset(this, 0, sizeof(ExFatFile));
|
|
m_attributes = FILE_ATTR_ROOT;
|
|
m_vol = vol;
|
|
m_flags = FILE_FLAG_READ;
|
|
return true;
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::parsePathName(const char* path, ExName_t* fname,
|
|
const char** ptr) {
|
|
// Skip leading spaces.
|
|
while (*path == ' ') {
|
|
path++;
|
|
}
|
|
fname->begin = path;
|
|
fname->end = path;
|
|
while (*path && !isDirSeparator(*path)) {
|
|
uint8_t c = *path++;
|
|
if (!lfnLegalChar(c)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (c != '.' && c != ' ') {
|
|
// Need to trim trailing dots spaces.
|
|
fname->end = path;
|
|
}
|
|
}
|
|
// Advance to next path component.
|
|
for (; *path == ' ' || isDirSeparator(*path); path++) {
|
|
}
|
|
*ptr = path;
|
|
return hashName(fname);
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int ExFatFile::peek() {
|
|
uint64_t saveCurPosition = m_curPosition;
|
|
uint32_t saveCurCluster = m_curCluster;
|
|
int c = read();
|
|
m_curPosition = saveCurPosition;
|
|
m_curCluster = saveCurCluster;
|
|
return c;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
int ExFatFile::read(void* buf, size_t count) {
|
|
uint8_t* dst = reinterpret_cast<uint8_t*>(buf);
|
|
int8_t fg;
|
|
size_t toRead = count;
|
|
size_t n;
|
|
uint8_t* cache;
|
|
uint16_t sectorOffset;
|
|
uint32_t sector;
|
|
uint32_t clusterOffset;
|
|
|
|
if (!isReadable()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (isContiguous() || isFile()) {
|
|
if ((m_curPosition + count) > m_validLength) {
|
|
count = toRead = m_validLength - m_curPosition;
|
|
}
|
|
}
|
|
while (toRead) {
|
|
clusterOffset = m_curPosition & m_vol->clusterMask();
|
|
sectorOffset = clusterOffset & m_vol->sectorMask();
|
|
if (clusterOffset == 0) {
|
|
if (m_curPosition == 0) {
|
|
m_curCluster =
|
|
isRoot() ? m_vol->rootDirectoryCluster() : m_firstCluster;
|
|
} else if (isContiguous()) {
|
|
m_curCluster++;
|
|
} else {
|
|
fg = m_vol->fatGet(m_curCluster, &m_curCluster);
|
|
if (fg < 0) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
if (fg == 0) {
|
|
// EOF if directory.
|
|
if (isDir()) {
|
|
break;
|
|
}
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
sector = m_vol->clusterStartSector(m_curCluster) +
|
|
(clusterOffset >> m_vol->bytesPerSectorShift());
|
|
if (sectorOffset != 0 || toRead < m_vol->bytesPerSector() ||
|
|
sector == m_vol->dataCacheSector()) {
|
|
n = m_vol->bytesPerSector() - sectorOffset;
|
|
if (n > toRead) {
|
|
n = toRead;
|
|
}
|
|
// read sector to cache and copy data to caller
|
|
cache = m_vol->dataCachePrepare(sector, FsCache::CACHE_FOR_READ);
|
|
if (!cache) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
uint8_t* src = cache + sectorOffset;
|
|
memcpy(dst, src, n);
|
|
#if USE_MULTI_SECTOR_IO
|
|
} else if (toRead >= 2 * m_vol->bytesPerSector()) {
|
|
uint32_t ns = toRead >> m_vol->bytesPerSectorShift();
|
|
// Limit reads to current cluster.
|
|
uint32_t maxNs = m_vol->sectorsPerCluster() -
|
|
(clusterOffset >> m_vol->bytesPerSectorShift());
|
|
if (ns > maxNs) {
|
|
ns = maxNs;
|
|
}
|
|
n = ns << m_vol->bytesPerSectorShift();
|
|
if (!m_vol->cacheSafeRead(sector, dst, ns)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
#endif // USE_MULTI_SECTOR_IO
|
|
} else {
|
|
// read single sector
|
|
n = m_vol->bytesPerSector();
|
|
if (!m_vol->cacheSafeRead(sector, dst)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
dst += n;
|
|
m_curPosition += n;
|
|
toRead -= n;
|
|
}
|
|
return count - toRead;
|
|
|
|
fail:
|
|
m_error |= READ_ERROR;
|
|
return -1;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::remove(const char* path) {
|
|
ExFatFile file;
|
|
if (!file.open(this, path, O_WRONLY)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
return file.remove();
|
|
|
|
fail:
|
|
return false;
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::seekSet(uint64_t pos) {
|
|
uint32_t nCur;
|
|
uint32_t nNew;
|
|
uint32_t tmp = m_curCluster;
|
|
// error if file not open
|
|
if (!isOpen()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// Optimize O_APPEND writes.
|
|
if (pos == m_curPosition) {
|
|
return true;
|
|
}
|
|
if (pos == 0) {
|
|
// set position to start of file
|
|
m_curCluster = 0;
|
|
goto done;
|
|
}
|
|
if (isFile()) {
|
|
if (pos > m_validLength) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
// calculate cluster index for new position
|
|
nNew = (pos - 1) >> m_vol->bytesPerClusterShift();
|
|
if (isContiguous()) {
|
|
m_curCluster = m_firstCluster + nNew;
|
|
goto done;
|
|
}
|
|
// calculate cluster index for current position
|
|
nCur = (m_curPosition - 1) >> m_vol->bytesPerClusterShift();
|
|
if (nNew < nCur || m_curPosition == 0) {
|
|
// must follow chain from first cluster
|
|
m_curCluster = isRoot() ? m_vol->rootDirectoryCluster() : m_firstCluster;
|
|
} else {
|
|
// advance from curPosition
|
|
nNew -= nCur;
|
|
}
|
|
while (nNew--) {
|
|
if (m_vol->fatGet(m_curCluster, &m_curCluster) <= 0) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
done:
|
|
m_curPosition = pos;
|
|
return true;
|
|
|
|
fail:
|
|
m_curCluster = tmp;
|
|
return false;
|
|
}
|