ufs: do not let a rolled back inode write end a data-only sync

With soft updates, ffs_syncvnode() writes the inode between its passes
while buffers are still dirty.  That write can go out with new block
pointers rolled back, for instance a new indirect block whose contents
are only written in a later pass, yet ffs_update() clears IN_SIZEMOD
and IN_IBLKDATA because the write was synchronous.  A data-only sync
then decides at the end that the inode need not be written, and
fdatasync(2) or an O_DSYNC write that allocated the first indirect
block returned with the on-disk inode not pointing to it.

Note on entry whether a data-only sync has to write the inode, and base
the final write, done once all dependencies are flushed, on that alone:
nothing else can set IN_SIZEMOD or IN_IBLKDATA meanwhile, as the vnode
is locked.  A full sync always writes the inode last and was not
affected.

Sponsored by:   Sippy Software, Inc.
Reviewed by:    kib
Tested by:      pho
Differential Revision:  https://reviews.freebsd.org/D60134
MFC after:      1 month
This commit is contained in:
Maxim Sobolev committed 2026-10-12 21:28:32 -07:00
1 parent 26d84bb8bf
commit e8534ba3ad
1 file changed
+9 -2
+9 -2
View File
@@ -271,11 +271,18 @@ ffs_syncvnode(struct vnode *vp, int waitfor, int flags)
struct buf *bp, *nbp;
ufs_lbn_t lbn;
int error, passes, wflag;
bool unlocked, wait;
bool inoupdt, unlocked, wait;
ip = VTOI(vp);
bo = &vp->v_bufobj;
ump = VFSTOUFS(vp->v_mount);
/*
* With soft updates, the passes below may write the inode while
* new block pointers in it are still rolled back. That clears
* IN_SIZEMOD and IN_IBLKDATA all the same, so note up front
* whether a data-only sync has to write the inode.
*/
inoupdt = (ip->i_flag & (IN_SIZEMOD | IN_IBLKDATA)) != 0;
#ifdef WITNESS
wflag = IS_SNAPSHOT(ip) ? LK_NOWITNESS : 0;
#else
@@ -442,7 +449,7 @@ next_locked:
error = ffs_update(vp, 1);
if (DOINGSUJ(vp))
softdep_journal_fsync(VTOI(vp));
} else if ((ip->i_flag & (IN_SIZEMOD | IN_IBLKDATA)) != 0) {
} else if (inoupdt) {
error = ffs_update(vp, 1);
}
if (error == 0 && unlocked)