text: /**
   * Copyright (c) 2011-2018 Bill Greiman
   * This file is part of the SdFs 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 "../common/DebugMacros.h"
  #include "ExFatFile.h"
  #include "ExFatVolume.h"
  #include "upcase.h"
  //=============================================================================
  #if READ_ONLY
  bool ExFatFile::sync() {
    return false;
  }
  bool ExFatFile::mkdir(ExFatFile* parent, const ExChar_t* path, bool pFlag) {
    (void) parent;
    (void)path;
    (void)pFlag;
    return false;
  }
  bool ExFatFile::preAllocate(uint64_t length) {
    (void)length;
    return false;
  }
  bool ExFatFile::rename(const ExChar_t* newPath) {
    (void)newPath;
    return false;
  }
  bool ExFatFile::rename(ExFatFile* dirFile, const ExChar_t* newPath) {
    (void)dirFile;
    (void)newPath;
    return false;
  }
  bool ExFatFile::truncate() {
    return false;
  }
  
  size_t ExFatFile::write(const void* buf, size_t nbyte) {
    (void)buf;
    (void)nbyte;
    return false;
  }
  //=============================================================================
  #else  // READ_ONLY
  //-----------------------------------------------------------------------------
  static uint16_t exFatDirChecksum(const uint8_t* data, uint16_t checksum) {
    bool skip = data[0] == EXFAT_TYPE_FILE;
    for (size_t i = 0; i < 32; i += i == 1 && skip ? 3 : 1) {
      checksum = ((checksum << 15) | (checksum >> 1)) + data[i];
    }
    return checksum;
  }
  //-----------------------------------------------------------------------------
  bool ExFatFile::addCluster() {
    uint32_t find = m_vol->bitmapFind(m_curCluster ?  m_curCluster + 1 : 0, 1);
    if (find < 2) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    if (!m_vol->bitmapModify(find, 1, 1)) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    if (m_curCluster == 0) {
      m_flags |= FILE_FLAG_CONTIGUOUS;
      goto done;
    }
    if (isContiguous()) {
      if (find == (m_curCluster + 1)) {
        goto done;
      }
      // No longer contiguous so make FAT chain.
      m_flags &= ~FILE_FLAG_CONTIGUOUS;
  
      for (uint32_t c = m_firstCluster; c < m_curCluster; c++) {
        if (!m_vol->fatPut(c, c + 1)) {
          DBG_FAIL_MACRO;
          goto fail;
        }
      }
    }
    // New cluster is EOC.
    if (!m_vol->fatPut(find, EXFAT_EOC)) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    // Connect new cluster to existing chain.
    if (m_curCluster) {
      if (!m_vol->fatPut(m_curCluster, find)) {
        DBG_FAIL_MACRO;
        goto fail;
      }
    }
  
   done:
    m_curCluster = find;
    return true;
  
   fail:
    return false;
  }
  //-----------------------------------------------------------------------------
  bool ExFatFile::addDirCluster() {
    uint32_t sector;
    uint32_t dl = isRoot() ? m_vol->rootLength() : m_dataLength;
    uint8_t* cache;
    dl += m_vol->bytesPerCluster();
    if (dl >= 0X4000000) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    if (!addCluster()) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    cache =  m_vol->cacheClear();
    if (!cache) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    memset(cache, 0, m_vol->bytesPerSector());
    sector = m_vol->clusterStartSector(m_curCluster);
    for (uint32_t i = 0; i < m_vol->sectorsPerCluster(); i++) {
      if (!m_vol->writeSector(sector + i, cache)) {
        DBG_FAIL_MACRO;
        goto fail;
      }
    }
    if (!isRoot()) {
      m_flags |= FILE_FLAG_DIR_DIRTY;
      m_dataLength  += m_vol->bytesPerCluster();
      m_validLength += m_vol->bytesPerCluster();
    }
    return sync();
  
   fail:
    return false;
  }
  //------------------------------------------------------------------------------
  bool ExFatFile::mkdir(ExFatFile* parent, const ExChar_t* path, bool pFlag) {
    ExName_t fname;
    ExFatFile 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 ExFatFile::mkdir(ExFatFile* parent, ExName_t* fname) {
    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_attributes = 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();
    m_flags = FILE_FLAG_READ | FILE_FLAG_CONTIGUOUS | FILE_FLAG_DIR_DIRTY;
    return sync();
  
  fail:
    return false;
  }
  //------------------------------------------------------------------------------
  bool ExFatFile::preAllocate(uint64_t length) {
    uint32_t find;
    uint32_t need;
    if (!length || !isWritable() || m_firstCluster) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    need = 1 + ((length - 1) >> m_vol->bytesPerClusterShift());
    find = m_vol->bitmapFind(0, need);
    if (find < 2) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    if (!m_vol->bitmapModify(find, need, 1)) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    m_dataLength = length;
    m_firstCluster = find;
    m_flags |= FILE_FLAG_DIR_DIRTY | FILE_FLAG_CONTIGUOUS;
    if (!sync()) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    return true;
  
   fail:
    return false;
  }
  //------------------------------------------------------------------------------
  bool ExFatFile::remove() {
    DirPos_t pos = m_dirPos;
    uint8_t* cache;
    if (!isWritable()) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    // Free any clusters.
    if (m_firstCluster) {
      if (isContiguous()) {
        uint32_t nc = 1 + ((m_dataLength - 1) >> m_vol->bytesPerClusterShift());
        if (!m_vol->bitmapModify(m_firstCluster, nc, 0)) {
          DBG_FAIL_MACRO;
          goto fail;
        }
      } else {
        if (!m_vol->freeChain(m_firstCluster)) {
          DBG_FAIL_MACRO;
          goto fail;
        }
      }
    }
  
    for (uint8_t i = 0; i <= m_setCount; i++) {
      if (i && m_vol->dirSeek(&pos, 32) != 1) {
        DBG_FAIL_MACRO;
        goto fail;
      }
      cache = m_vol->dirCache(&pos, FsCache::CACHE_FOR_WRITE);
      if (!cache) {
        DBG_FAIL_MACRO;
        goto fail;
      }
      // Mark entry not used.
      cache[0] &= 0x7F;
    }
    // Set this file closed.
    m_attributes = FILE_ATTR_CLOSED;
    m_flags = 0;
  
    // Write entry to device.
    return m_vol->cacheSync();
  
   fail:
    return false;
  }
  //------------------------------------------------------------------------------
  bool ExFatFile::rename(const ExChar_t* newPath) {
    return rename(m_vol->vwd(), newPath);
  }
  //------------------------------------------------------------------------------
  bool ExFatFile::rename(ExFatFile* dirFile, const ExChar_t* newPath) {
    ExFatFile file;
    ExFatFile oldFile;
  
    // Must be an open file or subdirectory.
    if (!(isFile() || isSubDir())) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    // Can't move file to new volume.
    if (m_vol != dirFile->m_vol) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    if (!file.open(dirFile, newPath, O_CREAT | O_EXCL | O_WRITE)) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    oldFile = *this;
    m_dirPos = file.m_dirPos;
    m_setCount = file.m_setCount;
    m_flags |= FILE_FLAG_DIR_DIRTY;
    if (!sync()) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    // Remove old directory entry;
    oldFile.m_firstCluster = 0;
    oldFile.m_flags = FILE_FLAG_WRITE;
    oldFile.m_attributes = FILE_ATTR_FILE;
    return oldFile.remove();
  
  fail:
    return false;
  }
  //------------------------------------------------------------------------------
  bool ExFatFile::rmdir() {
    int n;
    uint8_t dir[32];
    // must be open subdirectory
    if (!isSubDir()) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    rewind();
  
    // make sure directory is empty
    while (1) {
      n = read(dir, 32);
      if (n == 0) {
        break;
      }
      if (n != 32 || dir[0] & 0X80) {
        DBG_FAIL_MACRO;
        goto fail;
      }
      if (dir[0] == 0) {
        break;
      }
    }
    // convert empty directory to normal file for remove
    m_attributes = FILE_ATTR_FILE;
    m_flags |= FILE_FLAG_WRITE;
    return remove();
  
  fail:
    return false;
  }
  //-----------------------------------------------------------------------------
  bool ExFatFile::sync() {
    if (!isOpen()) {
      return true;
    }
    if (m_flags & FILE_FLAG_DIR_DIRTY) {
      // clear directory dirty
      m_flags &= ~FILE_FLAG_DIR_DIRTY;
      return syncDir();
    }
    if (!m_vol->cacheSync()) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    return true;
  
   fail:
    m_error |= WRITE_ERROR;
    return false;
  }
  //-----------------------------------------------------------------------------
  bool ExFatFile::syncDir() {
    DirFile_t* df;
    DirStream_t* ds;
    uint8_t* cache;
    uint16_t checksum = 0;
    uint8_t setCount = 0;
  
    DirPos_t pos = m_dirPos;
  
    for (uint8_t i = 0;; i++) {
      cache = m_vol->dirCache(&pos, FsCache::CACHE_FOR_READ);
      if (!cache) {
        DBG_FAIL_MACRO;
        goto fail;
      }
      switch (cache[0]) {
        case EXFAT_TYPE_FILE:
          df = reinterpret_cast<DirFile_t*>(cache);
          setCount = df->setCount;
          setLe16(df->attributes, m_attributes & FILE_ATTR_COPY);
          if (FsDateTime::callback) {
            m_vol->dataCacheDirty();
            uint16_t date, time;
            FsDateTime::callback(&date, &time);
            setLe16(df->modifyTime, time);
            setLe16(df->modifyDate, date);
            setLe16(df->accessTime, time);
            setLe16(df->accessDate, date);
          }
          break;
  
        case EXFAT_TYPE_STREAM:
          ds = reinterpret_cast<DirStream_t*>(cache);
          if (isContiguous()) {
            ds->flags |= EXFAT_FLAG_CONTIGUOUS;
          } else {
            ds->flags &= ~EXFAT_FLAG_CONTIGUOUS;
          }
          setLe64(ds->validLength, m_validLength);
          setLe32(ds->firstCluster, m_firstCluster);
          setLe64(ds->dataLength, m_dataLength);
          m_vol->dataCacheDirty();
          break;
  
        case EXFAT_TYPE_NAME:
          break;
  
        default:
          DBG_FAIL_MACRO;
          goto fail;
          break;
      }
      checksum = exFatDirChecksum(cache, checksum);
      if (i == setCount) break;
      if (m_vol->dirSeek(&pos, 32) != 1) {
        DBG_FAIL_MACRO;
        goto fail;
      }
    }
    df = reinterpret_cast<DirFile_t *>
         (m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_WRITE));
    if (!df) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    setLe16(df->setChecksum, checksum);
    if (!m_vol->cacheSync()) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    return true;
  
   fail:
    m_error |= WRITE_ERROR;
    return false;
  }
  //------------------------------------------------------------------------------
  bool ExFatFile::truncate() {
    uint32_t toFree;
    // error if not a normal file or read-only
    if (!isWritable()) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    if (m_firstCluster == 0) {
        return true;
    }
    if (isContiguous()) {
      uint32_t nc = 1 + ((m_dataLength - 1) >> m_vol->bytesPerClusterShift());
      if (m_curCluster) {
        toFree = m_curCluster + 1;
        nc -= 1 + m_curCluster - m_firstCluster;
      } else {
        toFree = m_firstCluster;
        m_firstCluster = 0;
      }
      if (nc && !m_vol->bitmapModify(toFree, nc, 0)) {
        DBG_FAIL_MACRO;
        goto fail;
      }
    } else {
      // need to free chain
      if (m_curCluster) {
        toFree = 0;
        int8_t fg = m_vol->fatGet(m_curCluster, &toFree);
        if (fg < 0) {
          DBG_FAIL_MACRO;
          goto fail;
        }
        if (fg) {
          // current cluster is end of chain
          if (!m_vol->fatPut(m_curCluster, EXFAT_EOC)) {
            DBG_FAIL_MACRO;
            goto fail;
          }
        }
      } else {
        toFree = m_firstCluster;
        m_firstCluster = 0;
      }
      if (toFree) {
        if (!m_vol->freeChain(toFree)) {
          DBG_FAIL_MACRO;
          goto fail;
        }
      }
    }
    m_dataLength = m_curPosition;
    m_validLength = m_curPosition;
    m_flags |= FILE_FLAG_DIR_DIRTY;
    return sync();
  
   fail:
    return false;
  }
  //------------------------------------------------------------------------------
  size_t ExFatFile::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);
    uint8_t* cache;
    uint8_t cacheOption;
    uint16_t sectorOffset;
    uint32_t sector;
    uint32_t clusterOffset;
  
    // number of bytes left to write  -  must be before goto statements
    size_t toWrite = nbyte;
    size_t n;
    // error if not an open file or is read-only
    if (!isWritable()) {
      DBG_FAIL_MACRO;
      goto fail;
    }
    // seek to end of file if append flag
    if ((m_flags & O_APPEND)) {
      if (!seekSet(m_validLength)) {
        DBG_FAIL_MACRO;
        goto fail;
      }
    }
    while (toWrite) {
      clusterOffset = m_curPosition & m_vol->clusterMask();
      sectorOffset = clusterOffset & m_vol->sectorMask();
      if (clusterOffset == 0) {
        // start of new cluster
        if (m_curCluster != 0) {
          int fg;
  
          if (isContiguous()) {
            uint32_t lc = m_firstCluster;
            lc += (m_dataLength - 1) >> m_vol->bytesPerClusterShift();
            if (m_curCluster < lc) {
              m_curCluster++;
              fg = 1;
            } else {
              fg = 0;
            }
          } else {
            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;
          }
        }
      }
      // sector for data write
      sector = m_vol->clusterStartSector(m_curCluster) +
               (clusterOffset >> m_vol->bytesPerSectorShift());
  
      if (sectorOffset != 0 || toWrite < m_vol->bytesPerSector()) {
        // partial sector - must use cache
        // max space in sector
        n = m_vol->bytesPerSector() - sectorOffset;
        // lesser of space and amount to write
        if (n > toWrite) {
          n = toWrite;
        }
  
        if (sectorOffset == 0 && m_curPosition >= m_validLength) {
          // start of new sector don't need to read into cache
          cacheOption = FsCache::CACHE_RESERVE_FOR_WRITE;
        } else {
          // rewrite part of sector
          cacheOption = FsCache::CACHE_FOR_WRITE;
        }
        cache = m_vol->dataCacheGet(sector, cacheOption);
        if (!cache) {
          DBG_FAIL_MACRO;
          goto fail;
        }
        uint8_t* dst = cache + sectorOffset;
        memcpy(dst, src, n);
        if (m_vol->bytesPerSector() == (n + sectorOffset)) {
          // Force write if sector is full - improves large writes.
          if (!m_vol->dataCacheSync()) {
            DBG_FAIL_MACRO;
            goto fail;
          }
        }
  #if USE_MULTI_SECTOR_IO
      } else if (toWrite >= 2*m_vol->bytesPerSector()) {
        // use multiple sector write command
        uint32_t ns = toWrite >> 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();
        if (m_vol->dataCacheSector() <= sector
            && m_vol->dataCacheSector() < (sector + ns)) {
          // invalidate cache if sector is in cache
          m_vol->dataCacheInvalidate();
        }
        if (!m_vol->writeSectors(sector, src, ns)) {
          DBG_FAIL_MACRO;
          goto fail;
        }
  #endif  // USE_MULTI_SECTOR_IO
      } else {
        // use single sector write command
        n = m_vol->bytesPerSector();
        if (m_vol->dataCacheSector() == sector) {
          m_vol->dataCacheInvalidate();
        }
        if (!m_vol->writeSector(sector, src)) {
          DBG_FAIL_MACRO;
          goto fail;
        }
      }
      m_curPosition += n;
      src += n;
      toWrite -= n;
      if (m_curPosition > m_validLength) {
        m_flags |= FILE_FLAG_DIR_DIRTY;
        m_validLength = m_curPosition;
      }
    }
  
    if (m_curPosition > m_dataLength) {
      m_dataLength = m_curPosition;
      // update fileSize and insure sync will update dir entr
      m_flags |= FILE_FLAG_DIR_DIRTY;
    } else if (FsDateTime::callback) {
      // insure sync will update modified date and time
      m_flags |= FILE_FLAG_DIR_DIRTY;
    }
    return nbyte;
  
  fail:
    // return for write error
    m_error |= WRITE_ERROR;
    return -1;
  }
  #endif  // READ_ONLY
  