/** * 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" #include "FatFileSystem.h" //------------------------------------------------------------------------------ // Pointer to cwd directory. FatFile* FatFile::m_cwd = 0; // Callback function for date/time. void (*FatFile::m_dateTime)(uint16_t* date, uint16_t* time) = 0; //------------------------------------------------------------------------------ // Add a cluster to a file. bool FatFile::addCluster() { m_flags |= F_FILE_DIR_DIRTY; return m_vol->allocateCluster(m_curCluster, &m_curCluster); } //------------------------------------------------------------------------------ // Add a cluster to a directory file and zero the cluster. // Return with first block of cluster in the cache. bool FatFile::addDirCluster() { uint32_t block; cache_t* pc; if (isRootFixed()) { DBG_FAIL_MACRO; goto fail; } // max folder size if (m_curPosition >= 512UL*4095) { DBG_FAIL_MACRO; goto fail; } if (!addCluster()) { DBG_FAIL_MACRO; goto fail; } block = m_vol->clusterFirstBlock(m_curCluster); pc = m_vol->cacheFetchData(block, FatCache::CACHE_RESERVE_FOR_WRITE); if (!pc) { DBG_FAIL_MACRO; goto fail; } memset(pc, 0, 512); // zero rest of clusters for (uint8_t i = 1; i < m_vol->blocksPerCluster(); i++) { if (!m_vol->writeBlock(block + i, pc->data)) { DBG_FAIL_MACRO; goto fail; } } // Set position to EOF to avoid inconsistent curCluster/curPosition. m_curPosition += 512UL*m_vol->blocksPerCluster(); return true; fail: return false; } //------------------------------------------------------------------------------ // cache a file's directory entry // return pointer to cached entry or null for failure dir_t* FatFile::cacheDirEntry(uint8_t action) { cache_t* pc; pc = m_vol->cacheFetchData(m_dirBlock, action); if (!pc) { DBG_FAIL_MACRO; goto fail; } return pc->dir + (m_dirIndex & 0XF); fail: return 0; } //------------------------------------------------------------------------------ bool FatFile::close() { bool rtn = sync(); m_attr = FILE_ATTR_CLOSED; return rtn; } //------------------------------------------------------------------------------ bool FatFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock) { // error if no blocks if (m_firstCluster == 0) { DBG_FAIL_MACRO; goto fail; } for (uint32_t c = m_firstCluster; ; c++) { uint32_t next; int8_t fg = m_vol->fatGet(c, &next); if (fg < 0) { DBG_FAIL_MACRO; goto fail; } // check for contiguous if (fg == 0 || next != (c + 1)) { // error if not end of chain if (fg) { DBG_FAIL_MACRO; goto fail; } *bgnBlock = m_vol->clusterFirstBlock(m_firstCluster); *endBlock = m_vol->clusterFirstBlock(c) + m_vol->blocksPerCluster() - 1; return true; } } fail: return false; } //------------------------------------------------------------------------------ bool FatFile::createContiguous(FatFile* dirFile, const char* path, uint32_t size, uint32_t startCluster) { uint32_t count; // don't allow zero length file if (size == 0) { DBG_FAIL_MACRO; goto fail; } if (!open(dirFile, path, O_RDWR | O_CREAT | O_EXCL)) { DBG_FAIL_MACRO; goto fail; } // calculate number of clusters needed count = ((size - 1) >> (m_vol->clusterSizeShift() + 9)) + 1; // allocate clusters if (!m_vol->allocContiguous(count, &m_firstCluster, startCluster)) { remove(); DBG_FAIL_MACRO; goto fail; } m_fileSize = size; // insure sync() will update dir entry m_flags |= F_FILE_DIR_DIRTY; return sync(); fail: return false; } //------------------------------------------------------------------------------ bool FatFile::dirEntry(dir_t* dst) { dir_t* dir; // Make sure fields on device are correct. if (!sync()) { DBG_FAIL_MACRO; goto fail; } // read entry dir = cacheDirEntry(FatCache::CACHE_FOR_READ); if (!dir) { DBG_FAIL_MACRO; goto fail; } // copy to caller's struct memcpy(dst, dir, sizeof(dir_t)); return true; fail: return false; } //------------------------------------------------------------------------------ uint8_t FatFile::dirName(const dir_t* dir, char* name) { uint8_t j = 0; uint8_t lcBit = DIR_NT_LC_BASE; for (uint8_t i = 0; i < 11; i++) { if (dir->name[i] == ' ') { continue; } if (i == 8) { // Position bit for extension. lcBit = DIR_NT_LC_EXT; name[j++] = '.'; } char c = dir->name[i]; if ('A' <= c && c <= 'Z' && (lcBit & dir->reservedNT)) { c += 'a' - 'A'; } name[j++] = c; } name[j] = 0; return j; } //------------------------------------------------------------------------------ uint32_t FatFile::dirSize() { int8_t fg; if (!isDir()) { return 0; } if (isRootFixed()) { return 32*m_vol->rootDirEntryCount(); } uint16_t n = 0; uint32_t c = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; do { fg = m_vol->fatGet(c, &c); if (fg < 0 || n > 4095) { return 0; } n += m_vol->blocksPerCluster(); } while (fg); return 512UL*n; } //------------------------------------------------------------------------------ int16_t FatFile::fgets(char* str, int16_t num, char* delim) { char ch; int16_t n = 0; int16_t 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 FatFile::getpos(FatPos_t* pos) { pos->position = m_curPosition; pos->cluster = m_curCluster; } //------------------------------------------------------------------------------ bool FatFile::mkdir(FatFile* parent, const char* path, bool pFlag) { fname_t fname; FatFile tmpDir; if (isOpen() || !parent || !parent->isDir()) { DBG_FAIL_MACRO; goto fail; } if (isDirSeparator(*path)) { while (isDirSeparator(*path)) { path++; } if (!tmpDir.openRoot(parent->m_vol)) { DBG_FAIL_MACRO; goto fail; } parent = &tmpDir; } while (1) { if (!parsePathName(path, &fname, &path)) { DBG_FAIL_MACRO; goto fail; } if (!*path) { break; } if (!open(parent, &fname, O_RDONLY)) { if (!pFlag || !mkdir(parent, &fname)) { DBG_FAIL_MACRO; goto fail; } } tmpDir = *this; parent = &tmpDir; close(); } return mkdir(parent, &fname); fail: return false; } //------------------------------------------------------------------------------ bool FatFile::mkdir(FatFile* parent, fname_t* fname) { uint32_t block; dir_t dot; dir_t* dir; cache_t* pc; if (!parent || !parent->isDir()) { DBG_FAIL_MACRO; goto fail; } // create a normal file if (!open(parent, fname, O_RDWR | O_CREAT | O_EXCL)) { DBG_FAIL_MACRO; goto fail; } // convert file to directory m_flags = F_READ; m_attr = FILE_ATTR_SUBDIR; // allocate and zero first cluster if (!addDirCluster()) { DBG_FAIL_MACRO; goto fail; } m_firstCluster = m_curCluster; // Set to start of dir rewind(); // force entry to device if (!sync()) { DBG_FAIL_MACRO; goto fail; } // cache entry - should already be in cache due to sync() call dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); if (!dir) { DBG_FAIL_MACRO; goto fail; } // change directory entry attribute dir->attributes = DIR_ATT_DIRECTORY; // make entry for '.' memcpy(&dot, dir, sizeof(dot)); dot.name[0] = '.'; for (uint8_t i = 1; i < 11; i++) { dot.name[i] = ' '; } // cache block for '.' and '..' block = m_vol->clusterFirstBlock(m_firstCluster); pc = m_vol->cacheFetchData(block, FatCache::CACHE_FOR_WRITE); if (!pc) { DBG_FAIL_MACRO; goto fail; } // copy '.' to block memcpy(&pc->dir[0], &dot, sizeof(dot)); // make entry for '..' dot.name[1] = '.'; dot.firstClusterLow = parent->m_firstCluster & 0XFFFF; dot.firstClusterHigh = parent->m_firstCluster >> 16; // copy '..' to block memcpy(&pc->dir[1], &dot, sizeof(dot)); // write first block return m_vol->cacheSync(); fail: return false; } //------------------------------------------------------------------------------ bool FatFile::open(FatFileSystem* fs, const char* path, oflag_t oflag) { return open(fs->vwd(), path, oflag); } //------------------------------------------------------------------------------ bool FatFile::open(FatFile* dirFile, const char* path, oflag_t oflag) { FatFile tmpDir; fname_t fname; // error if already open if (isOpen() || !dirFile || !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 FatFile::open(FatFile* dirFile, uint16_t index, oflag_t oflag) { uint8_t chksum = 0; uint8_t lfnOrd = 0; dir_t* dir; ldir_t*ldir; // Error if already open. if (isOpen() || !dirFile || !dirFile->isDir()) { DBG_FAIL_MACRO; goto fail; } // Don't open existing file if O_EXCL - user call error. if (oflag & O_EXCL) { DBG_FAIL_MACRO; goto fail; } if (index) { // Check for LFN. if (!dirFile->seekSet(32UL*(index -1))) { DBG_FAIL_MACRO; goto fail; } ldir = reinterpret_cast(dirFile->readDirCache()); if (!ldir) { DBG_FAIL_MACRO; goto fail; } if (ldir->attr == DIR_ATT_LONG_NAME) { if (1 == (ldir->ord & 0X1F)) { chksum = ldir->chksum; // Use largest possible number. lfnOrd = index > 20 ? 20 : index; } } } else { dirFile->rewind(); } // read entry into cache dir = dirFile->readDirCache(); if (!dir) { DBG_FAIL_MACRO; goto fail; } // error if empty slot or '.' or '..' if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == DIR_NAME_FREE || dir->name[0] == '.') { DBG_FAIL_MACRO; goto fail; } if (lfnOrd && chksum != lfnChecksum(dir->name)) { DBG_FAIL_MACRO; goto fail; } // open cached entry if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { DBG_FAIL_MACRO; goto fail; } return true; fail: return false; } //------------------------------------------------------------------------------ // open a cached directory entry. bool FatFile::openCachedEntry(FatFile* dirFile, uint16_t dirIndex, oflag_t oflag, uint8_t lfnOrd) { uint32_t firstCluster; memset(this, 0, sizeof(FatFile)); // location of entry in cache m_vol = dirFile->m_vol; m_dirIndex = dirIndex; m_dirCluster = dirFile->m_firstCluster; dir_t* dir = &m_vol->cacheAddress()->dir[0XF & dirIndex]; // Must be file or subdirectory. if (!DIR_IS_FILE_OR_SUBDIR(dir)) { DBG_FAIL_MACRO; goto fail; } m_attr = dir->attributes & FILE_ATTR_COPY; if (DIR_IS_FILE(dir)) { m_attr |= FILE_ATTR_FILE; } m_lfnOrd = lfnOrd; switch (oflag & O_ACCMODE) { case O_RDONLY: if (oflag & O_TRUNC) { DBG_FAIL_MACRO; goto fail; } m_flags = F_READ; break; case O_RDWR: m_flags = F_READ | F_WRITE; break; case O_WRONLY: m_flags = F_WRITE; break; default: DBG_FAIL_MACRO; goto fail; } if (m_flags & F_WRITE) { if (isSubDir() || isReadOnly()) { DBG_FAIL_MACRO; goto fail; } } m_flags |= (oflag & O_APPEND ? F_APPEND : 0) | (oflag & O_SYNC ? F_SYNC : 0); m_dirBlock = m_vol->cacheBlockNumber(); // copy first cluster number for directory fields firstCluster = ((uint32_t)dir->firstClusterHigh << 16) | dir->firstClusterLow; if (oflag & O_TRUNC) { if (firstCluster && !m_vol->freeChain(firstCluster)) { DBG_FAIL_MACRO; goto fail; } // need to update directory entry m_flags |= F_FILE_DIR_DIRTY; } else { m_firstCluster = firstCluster; m_fileSize = dir->fileSize; } if ((oflag & O_AT_END) && !seekSet(m_fileSize)) { DBG_FAIL_MACRO; goto fail; } return true; fail: m_attr = FILE_ATTR_CLOSED; return false; } //------------------------------------------------------------------------------ bool FatFile::openCwd() { if (!cwd()) { DBG_FAIL_MACRO; return false; } *this = *cwd(); rewind(); return true; } //------------------------------------------------------------------------------ bool FatFile::openNext(FatFile* dirFile, oflag_t oflag) { uint8_t chksum = 0; ldir_t* ldir; uint8_t lfnOrd = 0; uint16_t index; // Check for not open and valid directory. if (isOpen() || !dirFile || !dirFile->isDir() || (dirFile->curPosition() & 0X1F)) { DBG_FAIL_MACRO; goto fail; } while (1) { // read entry into cache index = dirFile->curPosition()/32; dir_t* dir = dirFile->readDirCache(); if (!dir) { if (dirFile->getError()) { DBG_FAIL_MACRO; } goto fail; } // done if last entry if (dir->name[0] == DIR_NAME_FREE) { goto fail; } // skip empty slot or '.' or '..' if (dir->name[0] == '.' || dir->name[0] == DIR_NAME_DELETED) { lfnOrd = 0; } else if (DIR_IS_FILE_OR_SUBDIR(dir)) { if (lfnOrd && chksum != lfnChecksum(dir->name)) { DBG_FAIL_MACRO; goto fail; } if (!openCachedEntry(dirFile, index, oflag, lfnOrd)) { DBG_FAIL_MACRO; goto fail; } return true; } else if (DIR_IS_LONG_NAME(dir)) { ldir = reinterpret_cast(dir); if (ldir->ord & LDIR_ORD_LAST_LONG_ENTRY) { lfnOrd = ldir->ord & 0X1F; chksum = ldir->chksum; } } else { lfnOrd = 0; } } fail: return false; } #ifndef DOXYGEN_SHOULD_SKIP_THIS //------------------------------------------------------------------------------ /** Open a file's parent directory. * * \param[in] file Parent of this directory will be opened. Must not be root. * * \return The value true is returned for success and * the value false is returned for failure. */ bool FatFile::openParent(FatFile* dirFile) { FatFile dotdot; uint32_t lbn; dir_t* dir; uint32_t ddc; cache_t* cb; if (isOpen() || !dirFile || !dirFile->isOpen()) { goto fail; } if (dirFile->m_dirCluster == 0) { return openRoot(dirFile->m_vol); } lbn = dirFile->m_vol->clusterFirstBlock(dirFile->m_dirCluster); cb = dirFile->m_vol->cacheFetchData(lbn, FatCache::CACHE_FOR_READ); if (!cb) { DBG_FAIL_MACRO; goto fail; } // Point to dir entery for .. dir = cb->dir + 1; ddc = dir->firstClusterLow | ((uint32_t)dir->firstClusterHigh << 16); if (ddc == 0) { if (!dotdot.openRoot(dirFile->m_vol)) { DBG_FAIL_MACRO; goto fail; } } else { memset(&dotdot, 0, sizeof(FatFile)); dotdot.m_attr = FILE_ATTR_SUBDIR; dotdot.m_flags = F_READ; dotdot.m_vol = dirFile->m_vol; dotdot.m_firstCluster = ddc; } uint32_t di; do { di = dotdot.curPosition()/32; dir = dotdot.readDirCache(); if (!dir) { DBG_FAIL_MACRO; goto fail; } ddc = dir->firstClusterLow | ((uint32_t)dir->firstClusterHigh << 16); } while (ddc != dirFile->m_dirCluster); return open(&dotdot, di, O_RDONLY); fail: return false; } #endif // DOXYGEN_SHOULD_SKIP_THIS //------------------------------------------------------------------------------ bool FatFile::openRoot(FatVolume* vol) { // error if file is already open if (isOpen()) { DBG_FAIL_MACRO; goto fail; } memset(this, 0, sizeof(FatFile)); m_vol = vol; switch (vol->fatType()) { #if FAT12_SUPPORT case 12: #endif // FAT12_SUPPORT case 16: m_attr = FILE_ATTR_ROOT_FIXED; break; case 32: m_attr = FILE_ATTR_ROOT32; break; default: DBG_FAIL_MACRO; goto fail; } // read only m_flags = F_READ; return true; fail: return false; } //------------------------------------------------------------------------------ int FatFile::peek() { FatPos_t pos; getpos(&pos); int c = read(); if (c >= 0) { setpos(&pos); } return c; } //------------------------------------------------------------------------------ int FatFile::read(void* buf, size_t nbyte) { int8_t fg; uint8_t blockOfCluster = 0; uint8_t* dst = reinterpret_cast(buf); uint16_t offset; size_t toRead; uint32_t block; // raw device block number cache_t* pc; // error if not open for read if (!isOpen() || !(m_flags & F_READ)) { DBG_FAIL_MACRO; goto fail; } if (isFile()) { uint32_t tmp32 = m_fileSize - m_curPosition; if (nbyte >= tmp32) { nbyte = tmp32; } } else if (isRootFixed()) { uint16_t tmp16 = 32*m_vol->m_rootDirEntryCount - (uint16_t)m_curPosition; if (nbyte > tmp16) { nbyte = tmp16; } } toRead = nbyte; while (toRead) { size_t n; offset = m_curPosition & 0X1FF; // offset in block if (isRootFixed()) { block = m_vol->rootDirStart() + (m_curPosition >> 9); } else { blockOfCluster = m_vol->blockOfCluster(m_curPosition); if (offset == 0 && blockOfCluster == 0) { // start of new cluster if (m_curPosition == 0) { // use first cluster in file m_curCluster = isRoot32() ? m_vol->rootDirStart() : m_firstCluster; } else { // get next cluster from FAT fg = m_vol->fatGet(m_curCluster, &m_curCluster); if (fg < 0) { DBG_FAIL_MACRO; goto fail; } if (fg == 0) { if (isDir()) { break; } DBG_FAIL_MACRO; goto fail; } } } block = m_vol->clusterFirstBlock(m_curCluster) + blockOfCluster; } if (offset != 0 || toRead < 512 || block == m_vol->cacheBlockNumber()) { // amount to be read from current block n = 512 - offset; if (n > toRead) { n = toRead; } // read block to cache and copy data to caller pc = m_vol->cacheFetchData(block, FatCache::CACHE_FOR_READ); if (!pc) { DBG_FAIL_MACRO; goto fail; } uint8_t* src = pc->data + offset; memcpy(dst, src, n); #if USE_MULTI_BLOCK_IO } else if (toRead >= 1024) { size_t nb = toRead >> 9; if (!isRootFixed()) { uint8_t mb = m_vol->blocksPerCluster() - blockOfCluster; if (mb < nb) { nb = mb; } } n = 512*nb; if (m_vol->cacheBlockNumber() <= block && block < (m_vol->cacheBlockNumber() + nb)) { // flush cache if a block is in the cache if (!m_vol->cacheSyncData()) { DBG_FAIL_MACRO; goto fail; } } if (!m_vol->readBlocks(block, dst, nb)) { DBG_FAIL_MACRO; goto fail; } #endif // USE_MULTI_BLOCK_IO } else { // read single block n = 512; if (!m_vol->readBlock(block, dst)) { DBG_FAIL_MACRO; goto fail; } } dst += n; m_curPosition += n; toRead -= n; } return nbyte - toRead; fail: m_error |= READ_ERROR; return -1; } //------------------------------------------------------------------------------ int8_t FatFile::readDir(dir_t* dir) { int16_t n; // if not a directory file or miss-positioned return an error if (!isDir() || (0X1F & m_curPosition)) { return -1; } while (1) { n = read(dir, sizeof(dir_t)); if (n != sizeof(dir_t)) { return n == 0 ? 0 : -1; } // last entry if DIR_NAME_FREE if (dir->name[0] == DIR_NAME_FREE) { return 0; } // skip empty entries and entry for . and .. if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { continue; } // return if normal file or subdirectory if (DIR_IS_FILE_OR_SUBDIR(dir)) { return n; } } } //------------------------------------------------------------------------------ // Read next directory entry into the cache // Assumes file is correctly positioned dir_t* FatFile::readDirCache(bool skipReadOk) { // uint8_t b; uint8_t i = (m_curPosition >> 5) & 0XF; if (i == 0 || !skipReadOk) { int8_t n = read(&n, 1); if (n != 1) { if (n != 0) { DBG_FAIL_MACRO; } goto fail; } m_curPosition += 31; } else { m_curPosition += 32; } // return pointer to entry return m_vol->cacheAddress()->dir + i; fail: return 0; } //------------------------------------------------------------------------------ bool FatFile::remove(FatFile* dirFile, const char* path) { FatFile file; if (!file.open(dirFile, path, O_WRONLY)) { DBG_FAIL_MACRO; goto fail; } return file.remove(); fail: return false; } //------------------------------------------------------------------------------ bool FatFile::rename(FatFile* dirFile, const char* newPath) { dir_t entry; uint32_t dirCluster = 0; FatFile file; FatFile oldFile; cache_t* pc; dir_t* dir; // Must be an open file or subdirectory. if (!(isFile() || isSubDir())) { DBG_FAIL_MACRO; goto fail; } // Can't rename LFN in 8.3 mode. if (!USE_LONG_FILE_NAMES && isLFN()) { DBG_FAIL_MACRO; goto fail; } // Can't move file to new volume. if (m_vol != dirFile->m_vol) { DBG_FAIL_MACRO; goto fail; } // sync() and cache directory entry sync(); oldFile = *this; dir = cacheDirEntry(FatCache::CACHE_FOR_READ); if (!dir) { DBG_FAIL_MACRO; goto fail; } // save directory entry memcpy(&entry, dir, sizeof(entry)); // make directory entry for new path if (isFile()) { if (!file.open(dirFile, newPath, O_WRONLY | O_CREAT | O_EXCL)) { DBG_FAIL_MACRO; goto fail; } } else { // don't create missing path prefix components if (!file.mkdir(dirFile, newPath, false)) { DBG_FAIL_MACRO; goto fail; } // save cluster containing new dot dot dirCluster = file.m_firstCluster; } // change to new directory entry m_dirBlock = file.m_dirBlock; m_dirIndex = file.m_dirIndex; m_lfnOrd = file.m_lfnOrd; m_dirCluster = file.m_dirCluster; // mark closed to avoid possible destructor close call file.m_attr = FILE_ATTR_CLOSED; // cache new directory entry dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); if (!dir) { DBG_FAIL_MACRO; goto fail; } // copy all but name and name flags to new directory entry memcpy(&dir->creationTimeTenths, &entry.creationTimeTenths, sizeof(entry) - sizeof(dir->name) - 2); dir->attributes = entry.attributes; // update dot dot if directory if (dirCluster) { // get new dot dot uint32_t block = m_vol->clusterFirstBlock(dirCluster); pc = m_vol->cacheFetchData(block, FatCache::CACHE_FOR_READ); if (!pc) { DBG_FAIL_MACRO; goto fail; } memcpy(&entry, &pc->dir[1], sizeof(entry)); // free unused cluster if (!m_vol->freeChain(dirCluster)) { DBG_FAIL_MACRO; goto fail; } // store new dot dot block = m_vol->clusterFirstBlock(m_firstCluster); pc = m_vol->cacheFetchData(block, FatCache::CACHE_FOR_WRITE); if (!pc) { DBG_FAIL_MACRO; goto fail; } memcpy(&pc->dir[1], &entry, sizeof(entry)); } // Remove old directory entry; oldFile.m_firstCluster = 0; oldFile.m_flags = F_WRITE; oldFile.m_attr = FILE_ATTR_FILE; if (!oldFile.remove()) { DBG_FAIL_MACRO; goto fail; } return m_vol->cacheSync(); fail: return false; } //------------------------------------------------------------------------------ bool FatFile::rmdir() { // must be open subdirectory if (!isSubDir() || (!USE_LONG_FILE_NAMES && isLFN())) { DBG_FAIL_MACRO; goto fail; } rewind(); // make sure directory is empty while (1) { dir_t* dir = readDirCache(true); if (!dir) { // EOF if no error. if (!getError()) { break; } DBG_FAIL_MACRO; goto fail; } // done if past last used entry if (dir->name[0] == DIR_NAME_FREE) { break; } // skip empty slot, '.' or '..' if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { continue; } // error not empty if (DIR_IS_FILE_OR_SUBDIR(dir)) { DBG_FAIL_MACRO; goto fail; } } // convert empty directory to normal file for remove m_attr = FILE_ATTR_FILE; m_flags |= F_WRITE; return remove(); fail: return false; } //------------------------------------------------------------------------------ bool FatFile::rmRfStar() { uint16_t index; FatFile f; if (!isDir()) { DBG_FAIL_MACRO; goto fail; } rewind(); while (1) { // remember position index = m_curPosition/32; dir_t* dir = readDirCache(); if (!dir) { // At EOF if no error. if (!getError()) { break; } DBG_FAIL_MACRO; goto fail; } // done if past last entry if (dir->name[0] == DIR_NAME_FREE) { break; } // skip empty slot or '.' or '..' if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') { continue; } // skip if part of long file name or volume label in root if (!DIR_IS_FILE_OR_SUBDIR(dir)) { continue; } if (!f.open(this, index, O_RDONLY)) { DBG_FAIL_MACRO; goto fail; } if (f.isSubDir()) { // recursively delete if (!f.rmRfStar()) { DBG_FAIL_MACRO; goto fail; } } else { // ignore read-only f.m_flags |= F_WRITE; if (!f.remove()) { DBG_FAIL_MACRO; goto fail; } } // position to next entry if required if (m_curPosition != (32UL*(index + 1))) { if (!seekSet(32UL*(index + 1))) { DBG_FAIL_MACRO; goto fail; } } } // don't try to delete root if (!isRoot()) { if (!rmdir()) { DBG_FAIL_MACRO; goto fail; } } return true; fail: return false; } //------------------------------------------------------------------------------ bool FatFile::seekSet(uint32_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_fileSize) { DBG_FAIL_MACRO; goto fail; } } else if (isRootFixed()) { if (pos <= 32*m_vol->rootDirEntryCount()) { goto done; } DBG_FAIL_MACRO; goto fail; } // calculate cluster index for cur and new position nCur = (m_curPosition - 1) >> (m_vol->clusterSizeShift() + 9); nNew = (pos - 1) >> (m_vol->clusterSizeShift() + 9); if (nNew < nCur || m_curPosition == 0) { // must follow chain from first cluster m_curCluster = isRoot32() ? m_vol->rootDirStart() : 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; } //------------------------------------------------------------------------------ void FatFile::setpos(FatPos_t* pos) { m_curPosition = pos->position; m_curCluster = pos->cluster; } //------------------------------------------------------------------------------ bool FatFile::sync() { if (!isOpen()) { return true; } if (m_flags & F_FILE_DIR_DIRTY) { dir_t* dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); // check for deleted by another open file object if (!dir || dir->name[0] == DIR_NAME_DELETED) { DBG_FAIL_MACRO; goto fail; } // do not set filesize for dir files if (isFile()) { dir->fileSize = m_fileSize; } // update first cluster fields dir->firstClusterLow = m_firstCluster & 0XFFFF; dir->firstClusterHigh = m_firstCluster >> 16; // set modify time if user supplied a callback date/time function if (m_dateTime) { m_dateTime(&dir->lastWriteDate, &dir->lastWriteTime); dir->lastAccessDate = dir->lastWriteDate; } // clear directory dirty m_flags &= ~F_FILE_DIR_DIRTY; } if (m_vol->cacheSync()) { return true; } DBG_FAIL_MACRO; fail: m_error |= WRITE_ERROR; return false; } //------------------------------------------------------------------------------ bool FatFile::timestamp(FatFile* file) { dir_t* dir; dir_t srcDir; // must be files to get timestamps if (!isFile() || !file || !file->isFile() || !file->dirEntry(&srcDir)) { DBG_FAIL_MACRO; goto fail; } // update directory fields if (!sync()) { DBG_FAIL_MACRO; goto fail; } dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); if (!dir) { DBG_FAIL_MACRO; goto fail; } // copy timestamps dir->lastAccessDate = srcDir.lastAccessDate; dir->creationDate = srcDir.creationDate; dir->creationTime = srcDir.creationTime; dir->creationTimeTenths = srcDir.creationTimeTenths; dir->lastWriteDate = srcDir.lastWriteDate; dir->lastWriteTime = srcDir.lastWriteTime; // write back entry return m_vol->cacheSync(); fail: return false; } //------------------------------------------------------------------------------ bool FatFile::timestamp(uint8_t flags, uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { uint16_t dirDate; uint16_t dirTime; dir_t* dir; if (!isFile() || year < 1980 || year > 2107 || month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) { DBG_FAIL_MACRO; goto fail; } // update directory entry if (!sync()) { DBG_FAIL_MACRO; goto fail; } dir = cacheDirEntry(FatCache::CACHE_FOR_WRITE); if (!dir) { DBG_FAIL_MACRO; goto fail; } dirDate = FAT_DATE(year, month, day); dirTime = FAT_TIME(hour, minute, second); if (flags & T_ACCESS) { dir->lastAccessDate = dirDate; } if (flags & T_CREATE) { dir->creationDate = dirDate; dir->creationTime = dirTime; // seems to be units of 1/100 second not 1/10 as Microsoft states dir->creationTimeTenths = second & 1 ? 100 : 0; } if (flags & T_WRITE) { dir->lastWriteDate = dirDate; dir->lastWriteTime = dirTime; } return m_vol->cacheSync(); fail: return false; } //------------------------------------------------------------------------------ bool FatFile::truncate(uint32_t length) { uint32_t newPos; // error if not a normal file or read-only if (!isFile() || !(m_flags & F_WRITE)) { DBG_FAIL_MACRO; goto fail; } // error if length is greater than current size if (length > m_fileSize) { DBG_FAIL_MACRO; goto fail; } // fileSize and length are zero - nothing to do if (m_fileSize == 0) { return true; } // remember position for seek after truncation newPos = m_curPosition > length ? length : m_curPosition; // position to last cluster in truncated file if (!seekSet(length)) { DBG_FAIL_MACRO; goto fail; } if (length == 0) { // free all clusters if (!m_vol->freeChain(m_firstCluster)) { DBG_FAIL_MACRO; goto fail; } m_firstCluster = 0; } else { uint32_t toFree; int8_t fg = m_vol->fatGet(m_curCluster, &toFree); if (fg < 0) { DBG_FAIL_MACRO; goto fail; } if (fg) { // free extra clusters if (!m_vol->freeChain(toFree)) { DBG_FAIL_MACRO; goto fail; } // current cluster is end of chain if (!m_vol->fatPutEOC(m_curCluster)) { DBG_FAIL_MACRO; goto fail; } } } m_fileSize = length; // need to update directory entry m_flags |= F_FILE_DIR_DIRTY; if (!sync()) { DBG_FAIL_MACRO; goto fail; } // set file to correct position return seekSet(newPos); fail: return false; } //------------------------------------------------------------------------------ int FatFile::write(const void* buf, size_t nbyte) { // convert void* to uint8_t* - must be before goto statements const uint8_t* src = reinterpret_cast(buf); cache_t* pc; uint8_t cacheOption; // number of bytes left to write - must be before goto statements size_t nToWrite = nbyte; size_t n; // error if not a normal file or is read-only if (!isFile() || !(m_flags & F_WRITE)) { DBG_FAIL_MACRO; goto fail; } // seek to end of file if append flag if ((m_flags & F_APPEND)) { if (!seekSet(m_fileSize)) { DBG_FAIL_MACRO; goto fail; } } // Don't exceed max fileSize. if (nbyte > (0XFFFFFFFF - m_curPosition)) { DBG_FAIL_MACRO; goto fail; } while (nToWrite) { uint8_t blockOfCluster = m_vol->blockOfCluster(m_curPosition); uint16_t blockOffset = m_curPosition & 0X1FF; if (blockOfCluster == 0 && blockOffset == 0) { // start of new cluster if (m_curCluster != 0) { int8_t fg = m_vol->fatGet(m_curCluster, &m_curCluster); if (fg < 0) { DBG_FAIL_MACRO; goto fail; } if (fg == 0) { // add cluster if at end of chain if (!addCluster()) { DBG_FAIL_MACRO; goto fail; } } } else { if (m_firstCluster == 0) { // allocate first cluster of file if (!addCluster()) { DBG_FAIL_MACRO; goto fail; } m_firstCluster = m_curCluster; } else { m_curCluster = m_firstCluster; } } } // block for data write uint32_t block = m_vol->clusterFirstBlock(m_curCluster) + blockOfCluster; if (blockOffset != 0 || nToWrite < 512) { // partial block - must use cache // max space in block n = 512 - blockOffset; // lesser of space and amount to write if (n > nToWrite) { n = nToWrite; } if (blockOffset == 0 && m_curPosition >= m_fileSize) { // start of new block don't need to read into cache cacheOption = FatCache::CACHE_RESERVE_FOR_WRITE; } else { // rewrite part of block cacheOption = FatCache::CACHE_FOR_WRITE; } pc = m_vol->cacheFetchData(block, cacheOption); if (!pc) { DBG_FAIL_MACRO; goto fail; } uint8_t* dst = pc->data + blockOffset; memcpy(dst, src, n); if (512 == (n + blockOffset)) { // Force write if block is full - improves large writes. if (!m_vol->cacheSyncData()) { DBG_FAIL_MACRO; goto fail; } } #if USE_MULTI_BLOCK_IO } else if (nToWrite >= 1024) { // use multiple block write command uint8_t maxBlocks = m_vol->blocksPerCluster() - blockOfCluster; size_t nb = nToWrite >> 9; if (nb > maxBlocks) { nb = maxBlocks; } n = 512*nb; if (m_vol->cacheBlockNumber() <= block && block < (m_vol->cacheBlockNumber() + nb)) { // invalidate cache if block is in cache m_vol->cacheInvalidate(); } if (!m_vol->writeBlocks(block, src, nb)) { DBG_FAIL_MACRO; goto fail; } #endif // USE_MULTI_BLOCK_IO } else { // use single block write command n = 512; if (m_vol->cacheBlockNumber() == block) { m_vol->cacheInvalidate(); } if (!m_vol->writeBlock(block, src)) { DBG_FAIL_MACRO; goto fail; } } m_curPosition += n; src += n; nToWrite -= n; } if (m_curPosition > m_fileSize) { // update fileSize and insure sync will update dir entry m_fileSize = m_curPosition; m_flags |= F_FILE_DIR_DIRTY; } else if (m_dateTime) { // insure sync will update modified date and time m_flags |= F_FILE_DIR_DIRTY; } if (m_flags & F_SYNC) { if (!sync()) { DBG_FAIL_MACRO; goto fail; } } return nbyte; fail: // return for write error m_error |= WRITE_ERROR; return -1; }