/** * 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 (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(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(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(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(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(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(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 (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(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; }