Page MenuHomeFreeBSD

D60355.1793745835.diff
No OneTemporary

Authored By
Unknown
Size
5 KB
Referenced Files
None
Subscribers
None

D60355.1793745835.diff

diff --git a/sys/ufs/ufs/ufs_vnops.c b/sys/ufs/ufs/ufs_vnops.c
--- a/sys/ufs/ufs/ufs_vnops.c
+++ b/sys/ufs/ufs/ufs_vnops.c
@@ -1236,6 +1236,28 @@
__DEVOLATILE(int *, &rename_restarts), 0,
"Times rename had to restart due to lock contention");
+/*
+ * A directory moved by ufs_rename() to a new parent could not have its ".."
+ * changed to name the new parent, so ".." still names the old one, which
+ * keeps its link. Take back the link added to the new parent, if one was,
+ * and cancel the journal record set up for it, which would otherwise never
+ * be written and would keep the new parent's link count rolled back in
+ * every write of its inode.
+ */
+static void
+ufs_rename_dotdot_undo(struct inode *tdp, struct inode *fip, bool addedlink)
+{
+
+ if (addedlink) {
+ tdp->i_effnlink--;
+ tdp->i_nlink--;
+ DIP_SET_NLINK(tdp, tdp->i_nlink);
+ UFS_INODE_SET_FLAG(tdp, IN_CHANGE);
+ }
+ if (DOINGSUJ(ITOV(tdp)))
+ softdep_revert_link(fip, tdp);
+}
+
/*
* Rename system call.
* rename("foo", "bar");
@@ -1743,13 +1765,16 @@
error = UFS_UPDATE(tdvp, !DOINGSOFTDEP(tdvp) &&
!DOINGASYNC(tdvp));
/* Don't go to bad here as the new link exists. */
- if (error)
+ if (error) {
+ ufs_rename_dotdot_undo(tdp, fip, true);
goto unlockout;
+ }
} else if (DOINGSUJ(tdvp))
/* Journal must account for each new link. */
softdep_setup_dotdot_link(tdp, fip);
SET_I_OFFSET(fip, mastertemplate.dot_reclen);
- (void)ufs_dirrewrite(fip, fdp, newparent, DT_DIR, 0);
+ if (ufs_dirrewrite(fip, fdp, newparent, DT_DIR, 0) != 0)
+ ufs_rename_dotdot_undo(tdp, fip, tip == NULL);
cache_purge(fdvp);
}
error = ufs_dirremove(fdvp, fip, fcnp->cn_flags, false);
diff --git a/tools/test/stress2/misc/rename19.sh b/tools/test/stress2/misc/rename19.sh
new file mode 100755
--- /dev/null
+++ b/tools/test/stress2/misc/rename19.sh
@@ -0,0 +1,107 @@
+#!/bin/sh
+
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 Maksym Sobolyev <sobomax@FreeBSD.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+# rename(2) of a directory to a new parent when ".." cannot be rewritten.
+#
+# ufs_rename() adds a link to the new parent for the moved directory's
+# "..", and with SU+J a journal record (jaddref) for it, and then rewrites
+# "..". When that rewrite failed, for example on a read error, ".." still
+# named the old parent, but the new parent kept the extra link and the
+# journal record was never written. With SU+J the record kept the new
+# parent's link count rolled back in every write of its inode, so the inode
+# block was redirtied forever: unmount failed with EAGAIN once fsync gave up
+# on it, and a forced unmount after I/O errors retried forever.
+# Without the journal, fsck found the new parent's link count too high.
+#
+# The read of the moved directory's block is made to fail with gnop(8),
+# after everything else the rename needs is cached. fsck_ffs then has to
+# report the stale "..", which shows that the rewrite failed, and nothing
+# else.
+
+[ `id -u` -ne 0 ] && echo "Must be root!" && exit 1
+
+. ../default.cfg
+
+kldstat -q -m g_nop || gnop load || exit 1
+log=/tmp/rename19.log
+s=0
+
+for flags in -U -j; do
+ set -e
+ mount | grep -q "on $mntpoint " && umount -f $mntpoint
+ mdconfig -l | grep -q "md$mdstart" && mdconfig -d -u $mdstart
+ mdconfig -a -t swap -s 1g -u $mdstart
+ newfs $flags /dev/md$mdstart > /dev/null
+ gnop create /dev/md$mdstart
+ mount /dev/md$mdstart.nop $mntpoint
+ mkdir -p $mntpoint/a/d $mntpoint/b
+ # Drop the cached blocks, then cache all but the block of a/d.
+ umount $mntpoint
+ mount /dev/md$mdstart.nop $mntpoint
+ set +e
+ ls -la $mntpoint $mntpoint/a $mntpoint/b > /dev/null
+ stat $mntpoint/a/d > /dev/null
+
+ gnop configure -r 100 md$mdstart.nop
+ mv $mntpoint/a/d $mntpoint/b/d
+ gnop configure -r 0 md$mdstart.nop
+
+ umount $mntpoint &
+ upid=$!
+ t=0
+ while kill -0 $upid 2>/dev/null; do
+ sleep 1
+ t=$((t + 1))
+ if [ $t -eq 120 ]; then
+ echo "FAIL ($flags): umount $mntpoint is stuck"
+ procstat -kk $upid
+ # The mount point stays busy, leave everything as is.
+ exit 1
+ fi
+ done
+ if ! wait $upid; then
+ # The suspension gave up on the inode block of b.
+ echo "FAIL ($flags): umount $mntpoint failed"
+ exit 1
+ fi
+
+ fsck_ffs -fn /dev/md$mdstart.nop > $log 2>&1
+ if ! grep -q "'\.\.'" $log; then
+ echo "SKIP ($flags): the rewrite of \"..\" did not fail"
+ cat $log
+ elif grep -q "LINK COUNT" $log; then
+ echo "FAIL ($flags): link count left wrong"
+ cat $log
+ s=1
+ fi
+ gnop destroy md$mdstart.nop
+ mdconfig -d -u $mdstart
+done
+rm -f $log
+exit $s

File Metadata

Mime Type
text/plain
Expires
Tue, Nov 3, 10:43 PM (14 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41387717
Default Alt Text
D60355.1793745835.diff (5 KB)

Event Timeline