From nobody Tue Oct 13 04:29:54 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4j3hFb2C67z6vglN for ; Tue, 13 Oct 2026 04:29:55 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR2" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4j3hFb16JDz4qWj for ; Tue, 13 Oct 2026 04:29:55 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1791865795; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=OohBkKMBOmHKIGqU1WrirhJVYshcNApmAYYVkgPsv4U=; b=FEfCkxOWO+896K0H7LlL+Uap0FCzvGiaIasCIgMBqO7LUCaDngkBfkK6qbGmt+yKwKP/H0 zqUQgjTfu/rI6KCBCXqryGg57aK1Q2ncepaWfxLIVCgCMSt4ApIHHgn4QF1bH70vBtE8c6 A5BsuaSO92QW+J8U9tkQU56szQZktZL2QPiVy3rc/CGZpS48Yk9wzT1pD58Lr4ZBdxiv0H tLIz/oCTpMJVWZ2pVID4sGvvkSfHV4wf3C0/CBYo54luMJCQ54avNuK/htOQmx/XSax6be GdnCEU3jNOk7N0g6zTzxlm1o7HBtzXCzXbQnbodPLDx0pgQM5t5RMY9yqcnTGg== ARC-Seal: i=1; a=rsa-sha256; d=freebsd.org; s=dkim; cv=none; t=1791865795; b=ZuSGFOyTPspxTRbJNbawim4jQm113Ci9T7c0WvkhUNHlX8Mmj+vqcZB1v6a9DD8wJdGUHp YA1zNtkh6yXWgD9sldw7FFHMus2pAShJhX2lUKxG0G9+8VEkVn5PelHKFC38uMZdoadZmR cplD8jrzi9az74xSL1DmTOE5/IAL79kXFzEjVqnCbEW/5ux33Z8PUKTJPObgHKvD4Y8jJ4 C4USEE7zftMmuI4hkUCwv9QtkS7QJdNCpWSAK1WNMKzAXY99fPwBF03Qg0/FiLJO1952M7 C6k9K/jVDYzNazrqMJ+NQBHefHPk1nHL5/Glsh/QcXmbx+CF3+nU2dMAfUB+RA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1791865795; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=OohBkKMBOmHKIGqU1WrirhJVYshcNApmAYYVkgPsv4U=; b=XokcaEXRqLq1W+nNYozxrgZHRxueFgGVUbk09s/UpXrbYDw/6Z9VhHvKxjnBQDIS/Oa9VT K9PCtQ5vXurwlJiS+f1k3GU955LYrtfqF29YxCwyUxgN4YuDhqGPg16H8p7hdO095PSPbB xNV0EB6iVbnQGU3gliyp05m/a8jWPFCUZ3TFVq141JNhz3+1TAaYUWhn77CTV9islztY0p 60GlfMRBlgyqzhw0PRIeFcN9K2a6unFnOQYa4lt9UI/eFzVCYHW1C9Go/gnzokAxLSHZmA 5CMeIc3YwYmGFwfKf50m0ITc77ulA9wD3HR/chr+PTN/o2Tke1dR2lfwoJlgKg== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4j3hFZ75dlz107M for ; Tue, 13 Oct 2026 04:29:54 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 31202 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 13 Oct 2026 04:29:54 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Maxim Sobolev Subject: git: 26d84bb8bf9b - main - ufs: flush soft updates dependencies for O_SYNC and O_DSYNC writes List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: Commit messages for the main branch of the src repository List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: sobomax X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 26d84bb8bf9b4acd0aa80b0208c1ba4fc9646ff3 Auto-Submitted: auto-generated Date: Tue, 13 Oct 2026 04:29:54 +0000 Message-Id: <6acdb3c2.31202.5e54a56e@gitrepo.freebsd.org> The branch main has been updated by sobomax: URL: https://cgit.FreeBSD.org/src/commit/?id=26d84bb8bf9b4acd0aa80b0208c1ba4fc9646ff3 commit 26d84bb8bf9b4acd0aa80b0208c1ba4fc9646ff3 Author: Maxim Sobolev AuthorDate: 2026-10-13 02:42:31 +0000 Commit: Maxim Sobolev CommitDate: 2026-10-13 04:28:32 +0000 ufs: flush soft updates dependencies for O_SYNC and O_DSYNC writes For a synchronous write, ffs_write() writes the data buffers with bwrite() and then the inode with ffs_update(vp, 1). With soft updates, a new block pointer can only reach the disk after the dependencies of the block, such as the cylinder group map that allocates it, are written. Until then initiate_write_inodeblock_ufs[12]() and initiate_write_indirdep() roll the pointer, and for an extending write the size, back in the buffer being written. Nothing in the write path flushes those dependencies, so the inode went out with the old size and pointers, ffs_update() cleared IN_SIZEMOD and IN_IBLKDATA, and a crash before the syncer rewrote the inode lost data that O_SYNC or O_DSYNC promised was on stable storage. The same held for a new block below an indirect block, which ffs_balloc() writes synchronously but with the pointer rolled back. When soft updates are enabled and the write allocated blocks, finish it with ffs_syncvnode(), as fsync(2) and fdatasync(2) do: it flushes the dependencies, rewrites the rolled back indirect blocks and writes the inode, or for O_DSYNC only writes the inode when IN_SIZEMOD or IN_IBLKDATA say it is needed. The flags cannot tell whether the write allocated anything, because allocating a block below an existing indirect block sets neither, so compare the inode's block count before and after the write instead. A write that allocated nothing, such as an overwrite of existing blocks, adds no dependencies of its own and keeps writing only the inode, so that it does not pay for a sync of the whole vnode. ffs_syncvnode() only drops the vnode lock when it has to resolve directory dependencies, which ffs_write() never sees. ffs_syncvnode() also writes whatever other descriptors left dirty on the vnode, so the first synchronous write after a lot of buffered writes would pay for all of them. To put that cost on the open instead, where it is less unexpected for a descriptor opened for synchronous writes, add ffs_open() and, with soft updates, sync the vnode there when a regular file is opened for writing with O_SYNC or O_DSYNC, the same way the writes would: a full sync for O_SYNC, a data-only one for O_DSYNC. It calls ffs_syncvnode() rather than VOP_FSYNC(), whose softdep_fsync() may drop the vnode lock in the middle of the open. The sync on open only moves work that the writes would otherwise do, so it is skipped rather than waited for when writes are suspended, and its errors do not fail the open. Without soft updates the writes sync nothing but their own blocks and the inode, so there is nothing to move and the open does not sync. 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 | 60 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/sys/ufs/ffs/ffs_vnops.c b/sys/ufs/ffs/ffs_vnops.c index 4044ecf20f92..493936578095 100644 --- a/sys/ufs/ffs/ffs_vnops.c +++ b/sys/ufs/ffs/ffs_vnops.c @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -117,6 +118,7 @@ static vop_lock1_t ffs_lock; #ifdef INVARIANTS static vop_unlock_t ffs_unlock_debug; #endif +static vop_open_t ffs_open; static vop_read_t ffs_read; static vop_write_t ffs_write; static int ffs_extread(struct vnode *vp, struct uio *uio, int ioflag); @@ -145,6 +147,7 @@ struct vop_vector ffs_vnodeops1 = { #ifdef INVARIANTS .vop_unlock = ffs_unlock_debug, #endif + .vop_open = ffs_open, .vop_read = ffs_read, .vop_reallocblks = ffs_reallocblks, .vop_write = ffs_write, @@ -180,6 +183,7 @@ struct vop_vector ffs_vnodeops2 = { #ifdef INVARIANTS .vop_unlock = ffs_unlock_debug, #endif + .vop_open = ffs_open, .vop_read = ffs_read, .vop_reallocblks = ffs_reallocblks, .vop_write = ffs_write, @@ -455,6 +459,41 @@ ffs_fdatasync(struct vop_fdatasync_args *ap) return (ffs_syncvnode(ap->a_vp, MNT_WAIT, DATA_ONLY)); } +/* + * With soft updates, an O_SYNC or O_DSYNC write that allocates blocks + * finishes with ffs_syncvnode(), which also writes whatever other + * descriptors left dirty on the vnode. Sync the vnode when it is opened + * for writing with either flag, so that this cost is mostly paid once, by + * the open, and not by the first synchronous write. + * + * This only moves work that the writes would otherwise do, so skip it + * rather than wait when writes are suspended, and do not fail the open on + * its errors. Without soft updates the writes sync nothing but their own + * blocks and the inode, so there is nothing to move and the open does not + * sync. A writable open locks the vnode exclusively. + */ +static int +ffs_open(struct vop_open_args *ap) +{ + struct vnode *vp; + struct mount *mp; + int error; + + error = VOP_OPEN_APV(&ufs_vnodeops, ap); + vp = ap->a_vp; + if (error != 0 || vp->v_type != VREG || !DOINGSOFTDEP(vp) || + (ap->a_mode & FWRITE) == 0 || + (ap->a_mode & (FFSYNC | FDSYNC)) == 0) + return (error); + ASSERT_VOP_ELOCKED(vp, "ffs_open"); + if (vn_start_write(vp, &mp, V_NOWAIT) != 0) + return (0); + (void)ffs_syncvnode(vp, MNT_WAIT, + (ap->a_mode & FFSYNC) != 0 ? 0 : DATA_ONLY); + vn_finished_write(mp); + return (0); +} + static int ffs_lock( struct vop_lock1_args /* { @@ -822,6 +861,7 @@ ffs_write( struct buf *bp; ufs_lbn_t lbn; off_t osize; + uint64_t oblocks; ssize_t resid, r; int seqcount; int blkoffset, error, flags, ioflag, size, xfersize; @@ -885,6 +925,7 @@ ffs_write( resid = uio->uio_resid; osize = ip->i_size; + oblocks = DIP(ip, i_blocks); if (seqcount > BA_SEQMAX) flags = BA_SEQMAX << BA_SEQSHIFT; else @@ -1023,7 +1064,24 @@ ffs_write( uio->uio_resid = resid; } } else if (resid > uio->uio_resid && (ioflag & IO_SYNC)) { - if (!(ioflag & IO_DATASYNC) || + if (DOINGSOFTDEP(vp) && DIP(ip, i_blocks) != oblocks) { + /* + * With soft updates, new block pointers in the + * inode and in indirect blocks are rolled back + * when those are written before the dependencies + * of the new blocks, such as the cylinder group + * maps, are on disk. If this write allocated + * blocks, let ffs_syncvnode() flush the + * dependencies first. It only drops the vnode + * lock for directories. A write that allocated + * nothing adds no dependencies of its own and only + * needs the inode written, as without soft updates. + */ + error = ffs_syncvnode(vp, MNT_WAIT, + (ioflag & IO_DATASYNC) != 0 ? DATA_ONLY : 0); + KASSERT(error != ERELOOKUP, + ("ffs_write: vnode %p unlocked by sync", vp)); + } else if (!(ioflag & IO_DATASYNC) || (ip->i_flag & (IN_SIZEMOD | IN_IBLKDATA))) error = ffs_update(vp, 1); if (ffs_fsfail_cleanup(VFSTOUFS(vp->v_mount), error))