git: 1498efc4243e - main - ufs: make fdatasync(2) write dirty indirect blocks

From: Maxim Sobolev <sobomax_at_FreeBSD.org>
Date: Tue, 13 Oct 2026 04:29:53 UTC
The branch main has been updated by sobomax:

URL: https://cgit.FreeBSD.org/src/commit/?id=1498efc4243e963503179547221335940e2aeded

commit 1498efc4243e963503179547221335940e2aeded
Author:     Maxim Sobolev <sobomax@FreeBSD.org>
AuthorDate: 2026-10-13 02:41:27 +0000
Commit:     Maxim Sobolev <sobomax@FreeBSD.org>
CommitDate: 2026-10-13 04:28:32 +0000

    ufs: make fdatasync(2) write dirty indirect blocks
    
    A data-only ffs_syncvnode() skips dirty indirect blocks on file systems
    without soft updates, and with soft updates stops making passes once
    only indirect blocks are left dirty.  Indirect blocks only get dirty
    when the block pointers in them change, and those pointers are what
    makes a newly allocated block reachable.  So when fdatasync() follows
    a write that extended the file or filled a hole past the direct blocks,
    the data and the inode size reached the disk but the pointer to the new
    block did not, and a crash lost the "synced" data.  With soft updates
    the indirect block was usually written once, with the new pointer
    rolled back, and never rewritten.
    
    POSIX requires fdatasync() to write the metadata needed to retrieve the
    data, which 52488b51489 already acknowledged for the inode, so treat
    dirty indirect blocks the same way for a data-only sync as for a full
    one.  This restores the loop to what it was before 2f514f92cf0.  Plain
    overwrites, the case data-only sync is meant to speed up, do not dirty
    indirect blocks and still write neither them nor the inode.
    
    Fixes:  2f514f92cf0 ("Implement VOP_FDATASYNC() for UFS.")
    
    Sponsored by:   Sippy Software, Inc.
    Reviewed by:    kib
    Tested by:      pho
    Differential Revision:  https://reviews.freebsd.org/D60134
    MFC after:      1 month
---
 sys/ufs/ffs/ffs_vnops.c | 50 +++++++++++++++----------------------------------
 1 file changed, 15 insertions(+), 35 deletions(-)

diff --git a/sys/ufs/ffs/ffs_vnops.c b/sys/ufs/ffs/ffs_vnops.c
index be708d07a2ca..4044ecf20f92 100644
--- a/sys/ufs/ffs/ffs_vnops.c
+++ b/sys/ufs/ffs/ffs_vnops.c
@@ -267,7 +267,7 @@ ffs_syncvnode(struct vnode *vp, int waitfor, int flags)
 	struct buf *bp, *nbp;
 	ufs_lbn_t lbn;
 	int error, passes, wflag;
-	bool still_dirty, unlocked, wait;
+	bool unlocked, wait;
 
 	ip = VTOI(vp);
 	bo = &vp->v_bufobj;
@@ -314,14 +314,13 @@ loop:
 		/*
 		 * Flush indirects in order, if requested.
 		 *
-		 * Note that if only datasync is requested, we can
-		 * skip indirect blocks when softupdates are not
-		 * active.  Otherwise we must flush them with data,
-		 * since dependencies prevent data block writes.
+		 * Indirect blocks are only dirtied by changes to the
+		 * block pointers they hold, so even a data-only sync
+		 * must write them: they are needed to retrieve newly
+		 * allocated blocks.
 		 */
 		if (waitfor == MNT_WAIT && bp->b_lblkno <= -UFS_NDADDR &&
-		    (lbn_level(bp->b_lblkno) >= passes ||
-		    ((flags & DATA_ONLY) != 0 && !DOINGSOFTDEP(vp))))
+		    lbn_level(bp->b_lblkno) >= passes)
 			continue;
 		if (bp->b_lblkno > lbn)
 			panic("ffs_syncvnode: syncing truncated data.");
@@ -421,35 +420,16 @@ next_locked:
 	 * these will be done with one sync and one async pass.
 	 */
 	if (bo->bo_dirty.bv_cnt > 0) {
-		if ((flags & DATA_ONLY) == 0) {
-			still_dirty = true;
-		} else {
-			/*
-			 * For data-only sync, dirty indirect buffers
-			 * are ignored.
-			 */
-			still_dirty = false;
-			TAILQ_FOREACH(bp, &bo->bo_dirty.bv_hd, b_bobufs) {
-				if (bp->b_lblkno > -UFS_NDADDR) {
-					still_dirty = true;
-					break;
-				}
-			}
-		}
-
-		if (still_dirty) {
-			/* Write the inode after sync passes to flush deps. */
-			if (wait && DOINGSOFTDEP(vp) &&
-			    (flags & NO_INO_UPDT) == 0) {
-				BO_UNLOCK(bo);
-				ffs_update(vp, 1);
-				BO_LOCK(bo);
-			}
-			/* switch between sync/async. */
-			wait = !wait;
-			if (wait || ++passes < UFS_NIADDR + 2)
-				goto loop;
+		/* Write the inode after sync passes to flush deps. */
+		if (wait && DOINGSOFTDEP(vp) && (flags & NO_INO_UPDT) == 0) {
+			BO_UNLOCK(bo);
+			ffs_update(vp, 1);
+			BO_LOCK(bo);
 		}
+		/* switch between sync/async. */
+		wait = !wait;
+		if (wait || ++passes < UFS_NIADDR + 2)
+			goto loop;
 	}
 	BO_UNLOCK(bo);
 	error = 0;