748 lines
19 KiB
C++
748 lines
19 KiB
C++
/**
|
|
* Copyright (c) 2011-2024 Bill Greiman
|
|
* This file is part of the SdFat library for SD memory cards.
|
|
*
|
|
* MIT License
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#define DBG_FILE "ExFatFileWrite.cpp"
|
|
#include "../common/DebugMacros.h"
|
|
#include "ExFatLib.h"
|
|
//==============================================================================
|
|
#if EXFAT_READ_ONLY
|
|
bool ExFatFile::mkdir(ExFatFile* parent, const char* 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 char* newPath) {
|
|
(void)newPath;
|
|
return false;
|
|
}
|
|
bool ExFatFile::rename(ExFatFile* dirFile, const char* newPath) {
|
|
(void)dirFile;
|
|
(void)newPath;
|
|
return false;
|
|
}
|
|
bool ExFatFile::sync() { return false; }
|
|
bool ExFatFile::truncate() { return false; }
|
|
size_t ExFatFile::write(const void* buf, size_t nbyte) {
|
|
(void)buf;
|
|
(void)nbyte;
|
|
return false;
|
|
}
|
|
//==============================================================================
|
|
#else // EXFAT_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;
|
|
}
|
|
sector = m_vol->clusterStartSector(m_curCluster);
|
|
for (uint32_t i = 0; i < m_vol->sectorsPerCluster(); i++) {
|
|
cache =
|
|
m_vol->dataCachePrepare(sector + i, FsCache::CACHE_RESERVE_FOR_WRITE);
|
|
if (!cache) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
memset(cache, 0, m_vol->bytesPerSector());
|
|
}
|
|
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 char* 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 (!openPrivate(parent, &fname, O_RDONLY)) {
|
|
if (!pFlag || !mkdir(parent, &fname)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
}
|
|
// tmpDir = *this;
|
|
tmpDir.copy(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 (!openPrivate(parent, fname, O_CREAT | O_EXCL | O_RDWR)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// convert file to directory
|
|
m_attributes = FILE_ATTR_SUBDIR | FS_ATTRIB_ARCHIVE;
|
|
|
|
// 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() {
|
|
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 is = 0; is <= m_setCount; is++) {
|
|
cache = dirCache(is, 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 char* newPath) {
|
|
return rename(m_vol->vwd(), newPath);
|
|
}
|
|
//------------------------------------------------------------------------------
|
|
bool ExFatFile::rename(ExFatFile* dirFile, const char* 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_WRONLY)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
// oldFile = *this;
|
|
oldFile.copy(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[FS_DIR_SIZE];
|
|
// must be open subdirectory
|
|
if (!isSubDir()) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
rewind();
|
|
|
|
// make sure directory is empty
|
|
while (1) {
|
|
n = read(dir, FS_DIR_SIZE);
|
|
if (n == 0) {
|
|
break;
|
|
}
|
|
if (n != FS_DIR_SIZE || 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;
|
|
|
|
for (uint8_t is = 0; is <= m_setCount; is++) {
|
|
cache = dirCache(is, FsCache::CACHE_FOR_READ);
|
|
if (!cache) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
switch (cache[0]) {
|
|
case EXFAT_TYPE_FILE:
|
|
df = reinterpret_cast<DirFile_t*>(cache);
|
|
setLe16(df->attributes, m_attributes & FS_ATTRIB_COPY);
|
|
if (FsDateTime::callback) {
|
|
uint16_t date, time;
|
|
uint8_t ms10;
|
|
FsDateTime::callback(&date, &time, &ms10);
|
|
df->modifyTimeMs = ms10;
|
|
setLe16(df->modifyTime, time);
|
|
setLe16(df->modifyDate, date);
|
|
setLe16(df->accessTime, time);
|
|
setLe16(df->accessDate, date);
|
|
}
|
|
m_vol->dataCacheDirty();
|
|
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);
|
|
}
|
|
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::timestamp(uint8_t flags, uint16_t year, uint8_t month,
|
|
uint8_t day, uint8_t hour, uint8_t minute,
|
|
uint8_t second) {
|
|
DirFile_t* df;
|
|
uint8_t* cache;
|
|
uint16_t checksum = 0;
|
|
uint16_t date;
|
|
uint16_t time;
|
|
uint8_t ms10;
|
|
|
|
if (!isFileOrSubDir() || 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;
|
|
}
|
|
|
|
date = FS_DATE(year, month, day);
|
|
time = FS_TIME(hour, minute, second);
|
|
ms10 = second & 1 ? 100 : 0;
|
|
|
|
for (uint8_t is = 0; is <= m_setCount; is++) {
|
|
cache = dirCache(is, FsCache::CACHE_FOR_READ);
|
|
if (!cache) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
switch (cache[0]) {
|
|
case EXFAT_TYPE_FILE:
|
|
df = reinterpret_cast<DirFile_t*>(cache);
|
|
setLe16(df->attributes, m_attributes & FS_ATTRIB_COPY);
|
|
m_vol->dataCacheDirty();
|
|
if (flags & T_ACCESS) {
|
|
setLe16(df->accessTime, time);
|
|
setLe16(df->accessDate, date);
|
|
}
|
|
if (flags & T_CREATE) {
|
|
df->createTimeMs = ms10;
|
|
setLe16(df->createTime, time);
|
|
setLe16(df->createDate, date);
|
|
}
|
|
if (flags & T_WRITE) {
|
|
df->modifyTimeMs = ms10;
|
|
setLe16(df->modifyTime, time);
|
|
setLe16(df->modifyDate, date);
|
|
}
|
|
break;
|
|
|
|
case EXFAT_TYPE_STREAM:
|
|
break;
|
|
|
|
case EXFAT_TYPE_NAME:
|
|
break;
|
|
|
|
default:
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
break;
|
|
}
|
|
checksum = exFatDirChecksum(cache, checksum);
|
|
}
|
|
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:
|
|
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 & FILE_FLAG_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->dataCachePrepare(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->cacheSafeWrite(sector, src, ns)) {
|
|
DBG_FAIL_MACRO;
|
|
goto fail;
|
|
}
|
|
#endif // USE_MULTI_SECTOR_IO
|
|
} else {
|
|
n = m_vol->bytesPerSector();
|
|
if (!m_vol->cacheSafeWrite(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 entry
|
|
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 0;
|
|
}
|
|
#endif // EXFAT_READ_ONLY
|