These changes implemnt FAT file shrinkage as needed to fully support ftruncate().
Squashed commit of the following: fs/fat: Resolves issues with truncating the cluster chain when shrinking files via ftruncate(). fs/fat: First cut at implementation file shrinkage logic needed to support ftruncate(). Certainly shrinks the file size but it does not appear to correctly disconnect the cluster chains. fs/fat: Restructure some functions in files to better support forthcoming file shrinkage logic. Put framework for file shrinkage in place. That logic is incomplete on initial commit.
This commit is contained in:
parent
5d1a91fd8e
commit
fb73006a4b
@ -260,7 +260,7 @@ static int fat_open(FAR struct file *filep, FAR const char *relpath,
|
||||
{
|
||||
/* Truncate the file to zero length */
|
||||
|
||||
ret = fat_dirtruncate(fs, &dirinfo);
|
||||
ret = fat_dirtruncate(fs, direntry);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout_with_semaphore;
|
||||
@ -1702,12 +1702,7 @@ static int fat_truncate(FAR struct file *filep, off_t length)
|
||||
FAR struct inode *inode;
|
||||
FAR struct fat_mountpt_s *fs;
|
||||
FAR struct fat_file_s *ff;
|
||||
int32_t cluster;
|
||||
off_t remaining;
|
||||
off_t oldsize;
|
||||
off_t pos;
|
||||
unsigned int zerosize;
|
||||
int sectndx;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
|
||||
@ -1750,172 +1745,72 @@ static int fat_truncate(FAR struct file *filep, off_t length)
|
||||
oldsize = ff->ff_size;
|
||||
if (oldsize == length)
|
||||
{
|
||||
/* Do nothing but say that we did */
|
||||
|
||||
ret = OK;
|
||||
goto errout_with_semaphore;
|
||||
}
|
||||
else if (oldsize > length)
|
||||
{
|
||||
/* We are shrinking the file */
|
||||
/* REVISIT: Logic to shrink the file has not yet been implemented */
|
||||
FAR uint8_t *direntry;
|
||||
int ndx;
|
||||
|
||||
ret = -ENOSYS;
|
||||
goto errout_with_semaphore;
|
||||
}
|
||||
/* We are shrinking the file. */
|
||||
/* Flush any unwritten data in the file buffer */
|
||||
|
||||
/* Otherwise we are extending the file. This is essentially the same as a
|
||||
* write except that (1) we write zeros and (2) we don't update the file
|
||||
* position.
|
||||
*/
|
||||
|
||||
pos = ff->ff_size;
|
||||
|
||||
/* Get the first sector to write to. */
|
||||
|
||||
if (!ff->ff_currentsector)
|
||||
{
|
||||
/* Has the starting cluster been defined? */
|
||||
|
||||
if (ff->ff_startcluster == 0)
|
||||
{
|
||||
/* No.. we have to create a new cluster chain */
|
||||
|
||||
ff->ff_startcluster = fat_createchain(fs);
|
||||
ff->ff_currentcluster = ff->ff_startcluster;
|
||||
ff->ff_sectorsincluster = fs->fs_fatsecperclus;
|
||||
}
|
||||
|
||||
/* The current sector can then be determined from the current cluster
|
||||
* and the file offset.
|
||||
*/
|
||||
|
||||
ret = fat_currentsector(fs, ff, pos);
|
||||
ret = fat_ffcacheflush(fs, ff);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout_with_semaphore;
|
||||
}
|
||||
|
||||
/* Read the directory entry into the fs_buffer. */
|
||||
|
||||
ret = fat_fscacheread(fs, ff->ff_dirsector);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout_with_semaphore;
|
||||
}
|
||||
|
||||
/* Recover a pointer to the specific directory entry in the sector
|
||||
* using the saved directory index.
|
||||
*/
|
||||
|
||||
ndx = (ff->ff_dirindex & DIRSEC_NDXMASK(fs)) * DIR_SIZE;
|
||||
direntry = &fs->fs_buffer[ndx];
|
||||
|
||||
/* Handle the simple case where we are shrinking the file to zero
|
||||
* length.
|
||||
*/
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
/* Shrink to length == 0 */
|
||||
|
||||
ret = fat_dirtruncate(fs, direntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Shrink to 0 < length < oldsize */
|
||||
|
||||
ret = fat_dirshrink(fs, direntry, length);
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop until either (1) the file has been fully extended with zeroed data
|
||||
* or (2) an error occurs. We assume we start with the current sector in
|
||||
* cache (ff_currentsector)
|
||||
*/
|
||||
|
||||
sectndx = pos & SEC_NDXMASK(fs);
|
||||
remaining = length - pos;
|
||||
|
||||
while (remaining > 0)
|
||||
else
|
||||
{
|
||||
/* Check if the current write stream has incremented to the next
|
||||
* cluster boundary
|
||||
/* Otherwise we are extending the file. This is essentially the same
|
||||
* as a write except that (1) we write zeros and (2) we don't update
|
||||
* the file position.
|
||||
*/
|
||||
|
||||
if (ff->ff_sectorsincluster < 1)
|
||||
ret = fat_dirextend(fs, ff, length);
|
||||
if (ret >= 0)
|
||||
{
|
||||
/* Extend the current cluster by one (unless lseek was used to
|
||||
* move the file position back from the end of the file)
|
||||
*/
|
||||
/* The truncation has completed without error. Update the file size */
|
||||
|
||||
cluster = fat_extendchain(fs, ff->ff_currentcluster);
|
||||
|
||||
/* Verify the cluster number */
|
||||
|
||||
if (cluster < 0)
|
||||
{
|
||||
ret = cluster;
|
||||
goto errout_with_semaphore;
|
||||
}
|
||||
else if (cluster < 2 || cluster >= fs->fs_nclusters)
|
||||
{
|
||||
ret = -ENOSPC;
|
||||
goto errout_with_semaphore;
|
||||
}
|
||||
|
||||
/* Setup to zero the first sector from the new cluster */
|
||||
|
||||
ff->ff_currentcluster = cluster;
|
||||
ff->ff_sectorsincluster = fs->fs_fatsecperclus;
|
||||
ff->ff_currentsector = fat_cluster2sector(fs, cluster);
|
||||
ff->ff_size = length;
|
||||
ret = OK;
|
||||
}
|
||||
|
||||
/* Decide whether we are performing a read-modify-write
|
||||
* operation, in which case we have to read the existing sector
|
||||
* into the buffer first.
|
||||
*
|
||||
* There are two cases where we can avoid this read:
|
||||
*
|
||||
* - If we are performing a whole-sector clear that was rejected
|
||||
* by fat_hwwrite(), i.e. sectndx == 0 and remaining >= sector size.
|
||||
*
|
||||
* - If the clear is aligned to the beginning of the sector and
|
||||
* extends beyond the end of the file, i.e. sectndx == 0 and
|
||||
* file pos + remaining >= file size.
|
||||
*/
|
||||
|
||||
if (sectndx == 0 && (remaining >= fs->fs_hwsectorsize ||
|
||||
(pos + remaining) >= ff->ff_size))
|
||||
{
|
||||
/* Flush unwritten data in the sector cache. */
|
||||
|
||||
ret = fat_ffcacheflush(fs, ff);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout_with_semaphore;
|
||||
}
|
||||
|
||||
/* Now mark the clean cache buffer as the current sector. */
|
||||
|
||||
ff->ff_cachesector = ff->ff_currentsector;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Read the current sector into memory (perhaps first flushing the
|
||||
* old, dirty sector to disk).
|
||||
*/
|
||||
|
||||
ret = fat_ffcacheread(fs, ff, ff->ff_currentsector);
|
||||
if (ret < 0)
|
||||
{
|
||||
goto errout_with_semaphore;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy the requested part of the sector from the user buffer */
|
||||
|
||||
zerosize = fs->fs_hwsectorsize - sectndx;
|
||||
if (zerosize > remaining)
|
||||
{
|
||||
/* We will not zero to the end of the sector. */
|
||||
|
||||
zerosize = remaining;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We will zero to the end of the buffer (or beyond). Bump up
|
||||
* the current sector number (actually the next sector number).
|
||||
*/
|
||||
|
||||
ff->ff_sectorsincluster--;
|
||||
ff->ff_currentsector++;
|
||||
}
|
||||
|
||||
/* Zero the data into the cached sector and make sure that the cached
|
||||
* sector is marked "dirty" so that it will be written back.
|
||||
*/
|
||||
|
||||
memset(&ff->ff_buffer[sectndx], 0, zerosize);
|
||||
ff->ff_bflags |= (FFBUFF_DIRTY | FFBUFF_VALID | FFBUFF_MODIFIED);
|
||||
|
||||
/* Set up for the next sector */
|
||||
|
||||
pos += zerosize;
|
||||
remaining -= zerosize;
|
||||
sectndx = pos & SEC_NDXMASK(fs);
|
||||
}
|
||||
|
||||
/* The truncation has completed without error. Update the file size */
|
||||
|
||||
ff->ff_size = length;
|
||||
ret = OK;
|
||||
}
|
||||
|
||||
errout_with_semaphore:
|
||||
fat_semgive(fs);
|
||||
|
@ -927,7 +927,11 @@ EXTERN int fat_dirname2path(struct fat_mountpt_s *fs, struct fs_dirent_s *dir
|
||||
|
||||
/* File creation and removal helpers */
|
||||
|
||||
EXTERN int fat_dirtruncate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo);
|
||||
EXTERN int fat_dirtruncate(struct fat_mountpt_s *fs, FAR uint8_t *direntry);
|
||||
EXTERN int fat_dirshrink(struct fat_mountpt_s *fs, FAR uint8_t *direntry,
|
||||
off_t length);
|
||||
EXTERN int fat_dirextend(FAR struct fat_mountpt_s *fs, FAR struct fat_file_s *ff,
|
||||
off_t length);
|
||||
EXTERN int fat_dircreate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo);
|
||||
EXTERN int fat_remove(struct fat_mountpt_s *fs, const char *relpath, bool directory);
|
||||
|
||||
|
@ -824,7 +824,7 @@ off_t fat_getcluster(struct fat_mountpt_s *fs, uint32_t clusterno)
|
||||
if (clusterno >= 2 && clusterno < fs->fs_nclusters)
|
||||
{
|
||||
/* Okay.. Read the next cluster from the FAT. The way we will do
|
||||
* this depends on the type of FAT filesystm we are dealing with.
|
||||
* this depends on the type of FAT filesystem we are dealing with.
|
||||
*/
|
||||
|
||||
switch (fs->fs_type)
|
||||
@ -1416,7 +1416,7 @@ int fat_nextdirentry(struct fat_mountpt_s *fs, struct fs_fatdir_s *dir)
|
||||
* Name: fat_dirtruncate
|
||||
*
|
||||
* Description:
|
||||
* Truncate an existing file to zero length
|
||||
* Truncate an existing file to zero length.
|
||||
*
|
||||
* Assumptions:
|
||||
* The caller holds mountpoint semaphore, fs_buffer holds the directory
|
||||
@ -1425,20 +1425,17 @@ int fat_nextdirentry(struct fat_mountpt_s *fs, struct fs_fatdir_s *dir)
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int fat_dirtruncate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
|
||||
int fat_dirtruncate(struct fat_mountpt_s *fs, FAR uint8_t *direntry)
|
||||
{
|
||||
unsigned int startcluster;
|
||||
uint32_t writetime;
|
||||
uint8_t *direntry;
|
||||
off_t savesector;
|
||||
int ret;
|
||||
uint32_t writetime;
|
||||
off_t savesector;
|
||||
int ret;
|
||||
|
||||
/* Get start cluster of the file to truncate */
|
||||
|
||||
direntry = &fs->fs_buffer[dirinfo->fd_seq.ds_offset];
|
||||
startcluster =
|
||||
((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
|
||||
DIR_GETFSTCLUSTLO(direntry);
|
||||
startcluster = ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
|
||||
DIR_GETFSTCLUSTLO(direntry);
|
||||
|
||||
/* Clear the cluster start value in the directory and set the file size
|
||||
* to zero. This makes the file look empty but also have to dispose of
|
||||
@ -1470,7 +1467,7 @@ int fat_dirtruncate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Setup FSINFO to reuse this cluster next */
|
||||
/* Setup FSINFO to reuse the old start cluster next */
|
||||
|
||||
fs->fs_fsinextfree = startcluster - 1;
|
||||
|
||||
@ -1479,6 +1476,280 @@ int fat_dirtruncate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo)
|
||||
return fat_fscacheread(fs, savesector);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fat_dirshrink
|
||||
*
|
||||
* Description:
|
||||
* Shrink the size existing file to a non-zero length
|
||||
*
|
||||
* Assumptions:
|
||||
* The caller holds mountpoint semaphore, fs_buffer holds the directory
|
||||
* entry.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int fat_dirshrink(struct fat_mountpt_s *fs, FAR uint8_t *direntry,
|
||||
off_t length)
|
||||
{
|
||||
off_t clustersize;
|
||||
off_t remaining;
|
||||
uint32_t writetime;
|
||||
int32_t lastcluster;
|
||||
int32_t cluster;
|
||||
int ret;
|
||||
|
||||
/* Get start cluster of the file to truncate */
|
||||
|
||||
lastcluster = ((uint32_t)DIR_GETFSTCLUSTHI(direntry) << 16) |
|
||||
DIR_GETFSTCLUSTLO(direntry);
|
||||
|
||||
/* Set the file size to the new length. */
|
||||
|
||||
DIR_PUTFILESIZE(direntry, length);
|
||||
|
||||
/* Set the ARCHIVE attribute and update the write time */
|
||||
|
||||
DIR_PUTATTRIBUTES(direntry, FATATTR_ARCHIVE);
|
||||
|
||||
writetime = fat_systime2fattime();
|
||||
DIR_PUTWRTTIME(direntry, writetime & 0xffff);
|
||||
DIR_PUTWRTDATE(direntry, writetime > 16);
|
||||
|
||||
/* This sector needs to be written back to disk eventually */
|
||||
|
||||
fs->fs_dirty = true;
|
||||
|
||||
/* Now find the cluster change to be removed. Start with the cluster
|
||||
* after the current one (which we know contains data).
|
||||
*/
|
||||
|
||||
cluster = fat_getcluster(fs, lastcluster);
|
||||
if (cluster < 0)
|
||||
{
|
||||
return cluster;
|
||||
}
|
||||
|
||||
clustersize = fs->fs_fatsecperclus * fs->fs_hwsectorsize;;
|
||||
remaining = length;
|
||||
|
||||
while (cluster >= 2 && cluster < fs->fs_nclusters)
|
||||
{
|
||||
/* Will there be data in the next cluster after the shrinkage? */
|
||||
|
||||
if (remaining <= clustersize)
|
||||
{
|
||||
/* No.. then nullify next cluster -- removing it from the
|
||||
* chain.
|
||||
*/
|
||||
|
||||
ret = fat_putcluster(fs, lastcluster, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Then free the remainder of the chain */
|
||||
|
||||
ret = fat_removechain(fs, cluster);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Setup FSINFO to reuse the removed cluster next */
|
||||
|
||||
fs->fs_fsinextfree = cluster - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Then set up to remove the next cluster */
|
||||
|
||||
lastcluster = cluster;
|
||||
cluster = fat_getcluster(fs, cluster);
|
||||
|
||||
if (cluster < 0)
|
||||
{
|
||||
return cluster;
|
||||
}
|
||||
|
||||
remaining -= clustersize;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fat_dirextend
|
||||
*
|
||||
* Description:
|
||||
* Zero-extend the length of a regular file to 'length'.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int fat_dirextend(FAR struct fat_mountpt_s *fs, FAR struct fat_file_s *ff,
|
||||
off_t length)
|
||||
{
|
||||
int32_t cluster;
|
||||
off_t remaining;
|
||||
off_t pos;
|
||||
unsigned int zerosize;
|
||||
int sectndx;
|
||||
int ret;
|
||||
|
||||
/* We are extending the file. This is essentially the same as a write
|
||||
* except that (1) we write zeros and (2) we don't update the file
|
||||
* position.
|
||||
*/
|
||||
|
||||
pos = ff->ff_size;
|
||||
|
||||
/* Get the first sector to write to. */
|
||||
|
||||
if (!ff->ff_currentsector)
|
||||
{
|
||||
/* Has the starting cluster been defined? */
|
||||
|
||||
if (ff->ff_startcluster == 0)
|
||||
{
|
||||
/* No.. we have to create a new cluster chain */
|
||||
|
||||
ff->ff_startcluster = fat_createchain(fs);
|
||||
ff->ff_currentcluster = ff->ff_startcluster;
|
||||
ff->ff_sectorsincluster = fs->fs_fatsecperclus;
|
||||
}
|
||||
|
||||
/* The current sector can then be determined from the current cluster
|
||||
* and the file offset.
|
||||
*/
|
||||
|
||||
ret = fat_currentsector(fs, ff, pos);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop until either (1) the file has been fully extended with zeroed data
|
||||
* or (2) an error occurs. We assume we start with the current sector in
|
||||
* cache (ff_currentsector)
|
||||
*/
|
||||
|
||||
sectndx = pos & SEC_NDXMASK(fs);
|
||||
remaining = length - pos;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
/* Check if the current write stream has incremented to the next
|
||||
* cluster boundary
|
||||
*/
|
||||
|
||||
if (ff->ff_sectorsincluster < 1)
|
||||
{
|
||||
/* Extend the current cluster by one (unless lseek was used to
|
||||
* move the file position back from the end of the file)
|
||||
*/
|
||||
|
||||
cluster = fat_extendchain(fs, ff->ff_currentcluster);
|
||||
|
||||
/* Verify the cluster number */
|
||||
|
||||
if (cluster < 0)
|
||||
{
|
||||
return (int)cluster;
|
||||
}
|
||||
else if (cluster < 2 || cluster >= fs->fs_nclusters)
|
||||
{
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
/* Setup to zero the first sector from the new cluster */
|
||||
|
||||
ff->ff_currentcluster = cluster;
|
||||
ff->ff_sectorsincluster = fs->fs_fatsecperclus;
|
||||
ff->ff_currentsector = fat_cluster2sector(fs, cluster);
|
||||
}
|
||||
|
||||
/* Decide whether we are performing a read-modify-write
|
||||
* operation, in which case we have to read the existing sector
|
||||
* into the buffer first.
|
||||
*
|
||||
* There are two cases where we can avoid this read:
|
||||
*
|
||||
* - If we are performing a whole-sector clear that was rejected
|
||||
* by fat_hwwrite(), i.e. sectndx == 0 and remaining >= sector size.
|
||||
*
|
||||
* - If the clear is aligned to the beginning of the sector and
|
||||
* extends beyond the end of the file, i.e. sectndx == 0 and
|
||||
* file pos + remaining >= file size.
|
||||
*/
|
||||
|
||||
if (sectndx == 0 && (remaining >= fs->fs_hwsectorsize ||
|
||||
(pos + remaining) >= ff->ff_size))
|
||||
{
|
||||
/* Flush unwritten data in the sector cache. */
|
||||
|
||||
ret = fat_ffcacheflush(fs, ff);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Now mark the clean cache buffer as the current sector. */
|
||||
|
||||
ff->ff_cachesector = ff->ff_currentsector;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Read the current sector into memory (perhaps first flushing the
|
||||
* old, dirty sector to disk).
|
||||
*/
|
||||
|
||||
ret = fat_ffcacheread(fs, ff, ff->ff_currentsector);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy the requested part of the sector from the user buffer */
|
||||
|
||||
zerosize = fs->fs_hwsectorsize - sectndx;
|
||||
if (zerosize > remaining)
|
||||
{
|
||||
/* We will not zero to the end of the sector. */
|
||||
|
||||
zerosize = remaining;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We will zero to the end of the buffer (or beyond). Bump up
|
||||
* the current sector number (actually the next sector number).
|
||||
*/
|
||||
|
||||
ff->ff_sectorsincluster--;
|
||||
ff->ff_currentsector++;
|
||||
}
|
||||
|
||||
/* Zero the data into the cached sector and make sure that the cached
|
||||
* sector is marked "dirty" so that it will be written back.
|
||||
*/
|
||||
|
||||
memset(&ff->ff_buffer[sectndx], 0, zerosize);
|
||||
ff->ff_bflags |= (FFBUFF_DIRTY | FFBUFF_VALID | FFBUFF_MODIFIED);
|
||||
|
||||
/* Set up for the next sector */
|
||||
|
||||
pos += zerosize;
|
||||
remaining -= zerosize;
|
||||
sectndx = pos & SEC_NDXMASK(fs);
|
||||
}
|
||||
|
||||
/* The truncation has completed without error. Update the file size */
|
||||
|
||||
ff->ff_size = length;
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fat_fscacheflush
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user