|
- /**
- * Copyright (c) 2011-2019 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 "ExFatFile.h"
- #include "ExFatVolume.h"
- #include "upcase.h"
- //-----------------------------------------------------------------------------
- bool ExFatFile::close() {
- bool rtn = sync();
- m_attributes = FILE_ATTR_CLOSED;
- m_flags = 0;
- return rtn;
- }
- //------------------------------------------------------------------------------
- void ExFatFile::fgetpos(fspos_t* pos) {
- 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;
- }
- //------------------------------------------------------------------------------
- void ExFatFile::fsetpos(const fspos_t* pos) {
- m_curPosition = pos->position;
- m_curCluster = pos->cluster;
- }
- //-----------------------------------------------------------------------------
- size_t ExFatFile::getName(ExChar_t *name, size_t length) {
- DirName_t* dn;
- DirPos_t pos = m_dirPos;
- size_t n = 0;
- if (!isOpen()) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- for (uint8_t is = 1; is < m_setCount; is++) {
- if (m_vol->dirSeek(&pos, is == 1 ? 64: 32) != 1) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- dn = reinterpret_cast<DirName_t*>
- (m_vol->dirCache(&pos, FsCache::CACHE_FOR_READ));
- if (!dn || dn->type != EXFAT_TYPE_NAME) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- for (uint8_t in = 0; in < 15; in++) {
- if ((n + 1) >= length) {
- goto done;
- }
- uint16_t c = getLe16(dn->unicode + 2*in);
- name[n++] = sizeof(ExChar_t) > 1 || c < 0X7F ? c : '?';
- }
- }
- done:
- name[n] = 0;
- return n;
-
- fail:
- *name = 0;
- return 0;
- }
- //-----------------------------------------------------------------------------
- bool ExFatFile::open(const ExChar_t* path, int oflag) {
- return open(ExFatVolume::cwv(), path, oflag);
- }
- //-----------------------------------------------------------------------------
- bool ExFatFile::open(ExFatVolume* vol, const ExChar_t* path, int oflag) {
- return vol && open(vol->vwd(), path, oflag);
- }
- //------------------------------------------------------------------------------
- bool ExFatFile::open(ExFatFile* dirFile, const ExChar_t* 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 (!open(dirFile, &fname, O_RDONLY)) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- tmpDir = *this;
- dirFile = &tmpDir;
- close();
- }
- return open(dirFile, &fname, oflag);
-
- fail:
- return false;
- }
- //------------------------------------------------------------------------------
- bool ExFatFile::open(ExFatFile* dirFile, uint32_t index, oflag_t oflag) {
- if (dirFile->seekSet(32*index) && openNext(dirFile, oflag)) {
- if (dirIndex() == index) {
- return true;
- }
- close();
- DBG_FAIL_MACRO;
- }
- return false;
- }
- //-----------------------------------------------------------------------------
- bool ExFatFile::openNext(ExFatFile* dir, oflag_t oflag) {
- if (isOpen() || !dir->isDir() || (dir->curPosition() & 0X1F)) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- return openRootFile(dir, nullptr, 0, oflag);
-
- fail:
- return false;
- }
- //------------------------------------------------------------------------------
- bool ExFatFile::openRootFile(ExFatFile* dir, const ExChar_t* name,
- uint8_t nameLength, oflag_t oflag) {
- int n;
- uint8_t nameOffset = 0;
- uint8_t nCmp;
- uint8_t modeFlags;
- uint16_t nameHash = 0;
- uint32_t curCluster __attribute__((unused));
- uint8_t* cache __attribute__((unused));
- DirPos_t freePos __attribute__((unused));
-
- DirFile_t* dirFile;
- DirStream_t* dirStream;
- DirName_t* dirName;
- uint8_t buf[32];
- uint8_t freeCount = 0;
- uint8_t freeNeed;
- bool inSet = false;
-
- // error if already open
- 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 (name) {
- nameHash = exFatHashName(name, nameLength, 0);
- dir->rewind();
- }
- freeNeed = 2 + (nameLength + 14)/15;
-
- while (1) {
- n = dir->read(buf, 32);
- if (n == 0) {
- goto create;
- }
- if (n != 32) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- if (!(buf[0] & 0x80)) {
- if (freeCount == 0) {
- freePos.position = dir->curPosition() - 32;
- freePos.cluster = dir->curCluster();
- }
- if (freeCount < freeNeed) {
- freeCount++;
- }
- if (!buf[0]) {
- goto create;
- }
- } else if (!inSet) {
- if (freeCount < freeNeed) {
- freeCount = 0;
- }
- if (buf[0] != EXFAT_TYPE_FILE) {
- continue;
- }
- inSet = true;
- }
- switch (buf[0]) {
- case EXFAT_TYPE_FILE:
- memset(this, 0, sizeof(ExFatFile));
- dirFile = reinterpret_cast<DirFile_t*>(buf);
- m_setCount = dirFile->setCount;
- m_attributes = getLe16(dirFile->attributes) & FILE_ATTR_COPY;
- if (!(m_attributes & EXFAT_ATTRIB_DIRECTORY)) {
- m_attributes |= FILE_ATTR_FILE;
- }
- m_vol = dir->volume();
-
- m_dirPos.cluster = dir->curCluster();
- m_dirPos.position = dir->curPosition() - 32;
- m_dirPos.isContiguous = dir->isContiguous();
- break;
-
- case EXFAT_TYPE_STREAM:
- dirStream = reinterpret_cast<DirStream_t*>(buf);
- m_flags = modeFlags;
- if (dirStream->flags & EXFAT_FLAG_CONTIGUOUS) {
- m_flags |= FILE_FLAG_CONTIGUOUS;
- }
- nameOffset = 0;
- m_validLength = getLe64(dirStream->validLength);
- m_firstCluster = getLe32(dirStream->firstCluster);
- m_dataLength = getLe64(dirStream->dataLength);
- if (!name) {
- goto found;
- }
- if (nameLength != dirStream->nameLength ||
- nameHash != getLe16(dirStream->nameHash)) {
- inSet = false;
- break;
- }
- break;
-
- case EXFAT_TYPE_NAME:
- dirName = reinterpret_cast<DirName_t*>(buf);
- nCmp = nameLength - nameOffset;
- if (nCmp > 15) {
- nCmp = 15;
- }
- if (!exFatCmpName(dirName, name, nameOffset, nCmp)) {
- inSet = false;
- break;
- }
- nameOffset += nCmp;
-
- if (nameOffset == nameLength) {
- goto found;
- }
- break;
-
- default:
- break;
- }
- }
-
- 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() || READ_ONLY) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- }
-
- #if !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;
- }
- #endif // READ_ONLY
- return true;
-
- create:
- #if READ_ONLY
- DBG_FAIL_MACRO;
- goto fail;
- #else // READ_ONLY
- // don't create unless O_CREAT and write
- if (!(oflag & O_CREAT) || !(modeFlags & FILE_FLAG_WRITE) || !name) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- while (freeCount < freeNeed) {
- n = dir->read(buf, 32);
- if (n == 0) {
- curCluster = dir->m_curCluster;
- if (!dir->addDirCluster()) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- dir->m_curCluster = curCluster;
- continue;
- }
- if (n != 32) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- if (freeCount == 0) {
- freePos.position = dir->curPosition() - 32;
- freePos.cluster = dir->curCluster();
- }
- freeCount++;
- }
-
- freePos.isContiguous = dir->isContiguous();
- memset(this, 0, sizeof(ExFatFile));
- m_vol = dir->volume();
- m_attributes = FILE_ATTR_FILE;
- m_dirPos = freePos;
- for (uint8_t i = 0; i < freeNeed; i++) {
- if (i) {
- if (1 != m_vol->dirSeek(&freePos, 32)) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- }
- cache = m_vol->dirCache(&freePos, FsCache::CACHE_FOR_WRITE);
- if (!cache || (cache[0] & 0x80)) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- memset(cache, 0 , 32);
- 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);
- dirFile->createTimeMs = ms10;
- setLe16(dirFile->createTime, time);
- setLe16(dirFile->createDate, date);
- }
- } else if (i == 1) {
- dirStream = reinterpret_cast<DirStream_t*>(cache);
- dirStream->type = EXFAT_TYPE_STREAM;
- dirStream->flags = EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS;
- m_flags = modeFlags | FILE_FLAG_CONTIGUOUS | FILE_FLAG_DIR_DIRTY;
-
- dirStream->nameLength = nameLength;
- setLe16(dirStream->nameHash, nameHash);
- } else {
- dirName = reinterpret_cast<DirName_t*>(cache);
- dirName->type = EXFAT_TYPE_NAME;
- nameOffset = 15*(i - 2);
- nCmp = nameLength - nameOffset;
- if (nCmp > 15) {
- nCmp = 15;
- }
- for (size_t k = 0; k < nCmp; k++) {
- setLe16(dirName->unicode + 2*k, name[k + nameOffset]);
- }
- }
- }
- return sync();
- #endif // 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 ExChar_t* path,
- ExName_t* fname, const ExChar_t** ptr) {
- ExChar_t c;
- int end;
- int len = 0;
-
- // 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 > EXFAT_MAX_NAME_LENGTH) {
- return false;
- }
- fname->len = len;
- return true;
- }
- //-----------------------------------------------------------------------------
- int ExFatFile::peek() {
- uint64_t curPosition = m_curPosition;
- uint32_t curCluster = m_curCluster;
- int c = read();
- m_curPosition = curPosition;
- m_curCluster = curCluster;
- return c;
- }
- //-----------------------------------------------------------------------------
- size_t ExFatFile::printName(print_t* pr) {
- DirName_t* dn;
- DirPos_t pos = m_dirPos;
- size_t n = 0;
- uint8_t in;
- uint8_t buf[15];
- if (!isOpen()) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- for (uint8_t is = 1; is < m_setCount; is++) {
- if (m_vol->dirSeek(&pos, is == 1 ? 64: 32) != 1) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- dn = reinterpret_cast<DirName_t*>
- (m_vol->dirCache(&pos, FsCache::CACHE_FOR_READ));
- if (!dn || dn->type != EXFAT_TYPE_NAME) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- for (in = 0; in < 15; in++) {
- uint16_t c = getLe16(dn->unicode + 2*in);
- if (!c) {
- break;;
- }
- buf[in] = c < 0X7f ? c : '?';
- n++;
- }
- pr->write(buf, in);
- }
- return n;
-
- fail:
- return 0;
- }
- //-----------------------------------------------------------------------------
- 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->dataCacheGet(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 writes to current cluster.
- uint32_t maxNs = m_vol->sectorsPerCluster()
- - (clusterOffset >> m_vol->bytesPerSectorShift());
- if (ns > maxNs) {
- ns = maxNs;
- }
- n = ns << m_vol->bytesPerSectorShift();
- // Check for cache sector in read range.
- if (sector <= m_vol->dataCacheSector()
- && m_vol->dataCacheSector() < (sector + ns)) {
- // Flush cache if cache sector is in the range.
- if (!m_vol->dataCacheSync()) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- }
- if (!m_vol->readSectors(sector, dst, ns)) {
- DBG_FAIL_MACRO;
- goto fail;
- }
- #endif // USE_MULTI_SECTOR_IO
- } else {
- // read single sector
- n = m_vol->bytesPerSector();
- if (!m_vol->readSector(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 ExChar_t* 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;
- }
|