This is an automated email from the ASF dual-hosted git repository.

casaroli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 7f2f3b63c098f13bd01a0ba485e87a12ecea60ba
Author: yushuailong <[email protected]>
AuthorDate: Thu Nov 5 21:41:35 2026 +0800

    fs/fat: Release newly allocated clusters after failed extension.
    
    A failed ftruncate() extension leaves the file length unchanged but
    can retain clusters allocated before space ran out. Resetting the
    traversal cursors alone does not restore the original allocation.
    
    Discard cached extension zeros before releasing clusters. Reuse the
    shared-chain trimming helper for non-empty files and remove the
    entire new chain for empty files. Apply rollback to failures from
    both EOF positioning and zero-extension, then reset shared cursors.
    Preserve the original error after successful rollback and propagate
    a rollback error if cleanup itself fails.
    
    Validated 1329 checks on FAT12, FAT16 and FAT32 RAM disks in both
    direct and forced-indirect I/O modes. Coverage checks free clusters,
    raw chains, file size, data, shared handles and remount persistence.
    The nucleo-h743zi:composite ARM build and image checks also passed.
    
    Assisted-by: Codex:GPT-6
    Signed-off-by: yushuailong <[email protected]>
---
 fs/fat/fs_fat32.c | 46 +++++++++++++++++++++++++++++++++++++---------
 1 file changed, 37 insertions(+), 9 deletions(-)

diff --git a/fs/fat/fs_fat32.c b/fs/fat/fs_fat32.c
index 061910cdda3..747f26898d5 100644
--- a/fs/fat/fs_fat32.c
+++ b/fs/fat/fs_fat32.c
@@ -346,9 +346,9 @@ static int fat_shared_release(FAR struct fat_mountpt_s *fs,
  * Name: fat_shrinkshared
  *
  * Description:
- *   Shrink a file described only by its shared (in-memory) state to a
- *   smaller, non-zero length.  Used when the directory entry is gone
- *   (unlinked file) and fat_dirshrink() has no entry to work with.
+ *   Trim the shared cluster chain to a non-zero length.  Used for unlinked
+ *   files and to release clusters allocated by a failed extension without
+ *   changing the directory entry.
  *
  *   The caller holds fs_lock and has already invalidated attached caches.
  *
@@ -364,8 +364,7 @@ static int fat_shrinkshared(FAR struct fat_mountpt_s *fs,
   int32_t cluster;
   int ret;
 
-  DEBUGASSERT(shared->s_pending && length > 0 &&
-              length < shared->s_size);
+  DEBUGASSERT(length > 0 && length <= shared->s_size);
 
   lastcluster = shared->s_startcluster;
 
@@ -2882,18 +2881,47 @@ static int fat_truncate(FAR struct file *filep, off_t 
length)
        * cluster of the file.
        */
 
+      ff->ff_bflags |= FFBUFF_MODIFIED;
+
       savepos      = filep->f_pos;
       filep->f_pos = oldsize;
       ret          = fat_get_sectors(filep, false);
       filep->f_pos = savepos;
+      if (ret >= 0)
+        {
+          ret = fat_dirextend(fs, ff, length);
+        }
+
       if (ret < 0)
         {
-          fat_shared_reset_cursors(fs, ff->ff_shared);
-          goto errout_with_lock;
+          int errcode = ret;
+
+          /* All pre-existing data was flushed before extension.  Discard
+           * cached zeros before releasing their clusters.
+           */
+
+          ff->ff_bflags     &= ~(FFBUFF_DIRTY | FFBUFF_VALID);
+          ff->ff_cachesector = 0;
+
+          if (oldsize == 0)
+            {
+              ret = fat_removechain(fs, ff->ff_shared->s_startcluster);
+              if (ret >= 0)
+                {
+                  ff->ff_shared->s_startcluster = 0;
+                }
+            }
+          else
+            {
+              ret = fat_shrinkshared(fs, ff, oldsize);
+            }
+
+          if (ret >= 0)
+            {
+              ret = errcode;
+            }
         }
 
-      ff->ff_bflags |= FFBUFF_MODIFIED;
-      ret = fat_dirextend(fs, ff, length);
       fat_shared_reset_cursors(fs, ff->ff_shared);
       if (ret >= 0)
         {

Reply via email to