diff --git a/fs/fat/fs_fat32.c b/fs/fat/fs_fat32.c index 3515b0d1fda..491d8641afb 100644 --- a/fs/fat/fs_fat32.c +++ b/fs/fat/fs_fat32.c @@ -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); diff --git a/fs/fat/fs_fat32.h b/fs/fat/fs_fat32.h index 5c4034e762b..84cf0751abc 100644 --- a/fs/fat/fs_fat32.h +++ b/fs/fat/fs_fat32.h @@ -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); diff --git a/fs/fat/fs_fat32util.c b/fs/fat/fs_fat32util.c index b57a4993779..669b65d09fd 100644 --- a/fs/fat/fs_fat32util.c +++ b/fs/fat/fs_fat32util.c @@ -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 *