/**
 * Copyright (c) 20011-2017 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 "../../../Repetier.h"
#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 count;

    // don't allow zero length file
    if (size == 0) {
        DBG_FAIL_MACRO;
        goto fail;
    }
    if (!open(dirFile, path, O_CREAT | O_EXCL | O_RDWR)) {
        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)) {
        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->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_READ)) {
            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->isDir()) {
        DBG_FAIL_MACRO;
        goto fail;
    }
    // create a normal file
    if (!open(parent, fname, O_CREAT | O_EXCL | O_RDWR)) {
        DBG_FAIL_MACRO;
        goto fail;
    }
    // convert file to directory
    m_flags = O_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, uint8_t oflag) {
    return open(fs->vwd(), path, oflag);
}
//------------------------------------------------------------------------------
bool FatFile::open(FatFile* dirFile, const char* path, uint8_t oflag) {
    FatFile tmpDir;
    fname_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_READ)) {
            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, uint8_t oflag) {
    uint8_t chksum = 0;
    uint8_t lfnOrd = 0;
    dir_t* dir;
    ldir_t* ldir;

    // Error if already open.
    if (isOpen() || !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<ldir_t*>(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,
                              uint8_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;
    // Write, truncate, or at end is an error for a directory or read-only file.
    if (oflag & (O_WRITE | O_TRUNC | O_AT_END)) {
        if (isSubDir() || isReadOnly()) {
            DBG_FAIL_MACRO;
            goto fail;
        }
    }
    // save open flags for read/write
    m_flags = oflag & F_OFLAG;

    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::openNext(FatFile* dirFile, uint8_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->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<ldir_t*>(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->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 = O_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_READ);

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 = O_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<uint8_t*>(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 & O_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_WRITE)) {
        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_CREAT | O_EXCL | O_WRITE)) {
            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 = O_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 |= O_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_READ)) {
            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 |= O_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;

    // most be files get timestamps
    if (!isFile() || !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 & O_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<const uint8_t*>(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 & O_WRITE)) {
        DBG_FAIL_MACRO;
        goto fail;
    }
    // seek to end of file if append flag
    if ((m_flags & O_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 & O_SYNC) {
        if (!sync()) {
            DBG_FAIL_MACRO;
            goto fail;
        }
    }
    return nbyte;

fail:
    // return for write error
    m_error |= WRITE_ERROR;
    return -1;
}
