mirror of
https://git.FreeBSD.org/src.git
synced 2025-01-24 16:10:11 +00:00
Prevent tmpfs_rename() deadlock in a way similar to UFS
Unlock vnodes and try to lock them one by one. Relookup fvp and tvp. Approved by: mdf (mentor)
This commit is contained in:
parent
ca846258e2
commit
db94ad126a
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=232960
@ -42,6 +42,7 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/proc.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/vnode.h>
|
||||
#include <sys/vmmeter.h>
|
||||
|
||||
@ -56,6 +57,8 @@ __FBSDID("$FreeBSD$");
|
||||
#include <fs/tmpfs/tmpfs_fifoops.h>
|
||||
#include <fs/tmpfs/tmpfs_vnops.h>
|
||||
|
||||
SYSCTL_NODE(_vfs, OID_AUTO, tmpfs, CTLFLAG_RW, 0, "tmpfs file system");
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
|
@ -46,6 +46,7 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/sf_buf.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
@ -57,6 +58,13 @@ __FBSDID("$FreeBSD$");
|
||||
#include <fs/tmpfs/tmpfs_vnops.h>
|
||||
#include <fs/tmpfs/tmpfs.h>
|
||||
|
||||
SYSCTL_DECL(_vfs_tmpfs);
|
||||
|
||||
static volatile int tmpfs_rename_restarts;
|
||||
SYSCTL_INT(_vfs_tmpfs, OID_AUTO, rename_restarts, CTLFLAG_RD,
|
||||
__DEVOLATILE(int *, &tmpfs_rename_restarts), 0,
|
||||
"Times rename had to restart due to lock contention");
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
static int
|
||||
@ -918,6 +926,139 @@ tmpfs_link(struct vop_link_args *v)
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* We acquire all but fdvp locks using non-blocking acquisitions. If we
|
||||
* fail to acquire any lock in the path we will drop all held locks,
|
||||
* acquire the new lock in a blocking fashion, and then release it and
|
||||
* restart the rename. This acquire/release step ensures that we do not
|
||||
* spin on a lock waiting for release. On error release all vnode locks
|
||||
* and decrement references the way tmpfs_rename() would do.
|
||||
*/
|
||||
static int
|
||||
tmpfs_rename_relock(struct vnode *fdvp, struct vnode **fvpp,
|
||||
struct vnode *tdvp, struct vnode **tvpp,
|
||||
struct componentname *fcnp, struct componentname *tcnp)
|
||||
{
|
||||
struct vnode *nvp;
|
||||
struct mount *mp;
|
||||
struct tmpfs_dirent *de;
|
||||
int error, restarts = 0;
|
||||
|
||||
VOP_UNLOCK(tdvp, 0);
|
||||
if (*tvpp != NULL && *tvpp != tdvp)
|
||||
VOP_UNLOCK(*tvpp, 0);
|
||||
mp = fdvp->v_mount;
|
||||
|
||||
relock:
|
||||
restarts += 1;
|
||||
error = vn_lock(fdvp, LK_EXCLUSIVE);
|
||||
if (error)
|
||||
goto releout;
|
||||
if (vn_lock(tdvp, LK_EXCLUSIVE | LK_NOWAIT) != 0) {
|
||||
VOP_UNLOCK(fdvp, 0);
|
||||
error = vn_lock(tdvp, LK_EXCLUSIVE);
|
||||
if (error)
|
||||
goto releout;
|
||||
VOP_UNLOCK(tdvp, 0);
|
||||
goto relock;
|
||||
}
|
||||
/*
|
||||
* Re-resolve fvp to be certain it still exists and fetch the
|
||||
* correct vnode.
|
||||
*/
|
||||
de = tmpfs_dir_lookup(VP_TO_TMPFS_DIR(fdvp), NULL, fcnp);
|
||||
if (de == NULL) {
|
||||
VOP_UNLOCK(fdvp, 0);
|
||||
VOP_UNLOCK(tdvp, 0);
|
||||
if ((fcnp->cn_flags & ISDOTDOT) != 0 ||
|
||||
(fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.'))
|
||||
error = EINVAL;
|
||||
else
|
||||
error = ENOENT;
|
||||
goto releout;
|
||||
}
|
||||
error = tmpfs_alloc_vp(mp, de->td_node, LK_EXCLUSIVE | LK_NOWAIT, &nvp);
|
||||
if (error != 0) {
|
||||
VOP_UNLOCK(fdvp, 0);
|
||||
VOP_UNLOCK(tdvp, 0);
|
||||
if (error != EBUSY)
|
||||
goto releout;
|
||||
error = tmpfs_alloc_vp(mp, de->td_node, LK_EXCLUSIVE, &nvp);
|
||||
if (error != 0)
|
||||
goto releout;
|
||||
VOP_UNLOCK(nvp, 0);
|
||||
/*
|
||||
* Concurrent rename race.
|
||||
*/
|
||||
if (nvp == tdvp) {
|
||||
vrele(nvp);
|
||||
error = EINVAL;
|
||||
goto releout;
|
||||
}
|
||||
vrele(*fvpp);
|
||||
*fvpp = nvp;
|
||||
goto relock;
|
||||
}
|
||||
vrele(*fvpp);
|
||||
*fvpp = nvp;
|
||||
VOP_UNLOCK(*fvpp, 0);
|
||||
/*
|
||||
* Re-resolve tvp and acquire the vnode lock if present.
|
||||
*/
|
||||
de = tmpfs_dir_lookup(VP_TO_TMPFS_DIR(tdvp), NULL, tcnp);
|
||||
/*
|
||||
* If tvp disappeared we just carry on.
|
||||
*/
|
||||
if (de == NULL && *tvpp != NULL) {
|
||||
vrele(*tvpp);
|
||||
*tvpp = NULL;
|
||||
}
|
||||
/*
|
||||
* Get the tvp ino if the lookup succeeded. We may have to restart
|
||||
* if the non-blocking acquire fails.
|
||||
*/
|
||||
if (de != NULL) {
|
||||
nvp = NULL;
|
||||
error = tmpfs_alloc_vp(mp, de->td_node,
|
||||
LK_EXCLUSIVE | LK_NOWAIT, &nvp);
|
||||
if (*tvpp != NULL)
|
||||
vrele(*tvpp);
|
||||
*tvpp = nvp;
|
||||
if (error != 0) {
|
||||
VOP_UNLOCK(fdvp, 0);
|
||||
VOP_UNLOCK(tdvp, 0);
|
||||
if (error != EBUSY)
|
||||
goto releout;
|
||||
error = tmpfs_alloc_vp(mp, de->td_node, LK_EXCLUSIVE,
|
||||
&nvp);
|
||||
if (error != 0)
|
||||
goto releout;
|
||||
VOP_UNLOCK(nvp, 0);
|
||||
/*
|
||||
* fdvp contains fvp, thus tvp (=fdvp) is not empty.
|
||||
*/
|
||||
if (nvp == fdvp) {
|
||||
error = ENOTEMPTY;
|
||||
goto releout;
|
||||
}
|
||||
goto relock;
|
||||
}
|
||||
}
|
||||
tmpfs_rename_restarts += restarts;
|
||||
|
||||
return (0);
|
||||
|
||||
releout:
|
||||
vrele(fdvp);
|
||||
vrele(*fvpp);
|
||||
vrele(tdvp);
|
||||
if (*tvpp != NULL)
|
||||
vrele(*tvpp);
|
||||
tmpfs_rename_restarts += restarts;
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
static int
|
||||
tmpfs_rename(struct vop_rename_args *v)
|
||||
{
|
||||
@ -927,6 +1068,7 @@ tmpfs_rename(struct vop_rename_args *v)
|
||||
struct vnode *tdvp = v->a_tdvp;
|
||||
struct vnode *tvp = v->a_tvp;
|
||||
struct componentname *tcnp = v->a_tcnp;
|
||||
struct mount *mp = NULL;
|
||||
|
||||
char *newname;
|
||||
int error;
|
||||
@ -942,8 +1084,6 @@ tmpfs_rename(struct vop_rename_args *v)
|
||||
MPASS(fcnp->cn_flags & HASBUF);
|
||||
MPASS(tcnp->cn_flags & HASBUF);
|
||||
|
||||
tnode = (tvp == NULL) ? NULL : VP_TO_TMPFS_NODE(tvp);
|
||||
|
||||
/* Disallow cross-device renames.
|
||||
* XXX Why isn't this done by the caller? */
|
||||
if (fvp->v_mount != tdvp->v_mount ||
|
||||
@ -952,9 +1092,6 @@ tmpfs_rename(struct vop_rename_args *v)
|
||||
goto out;
|
||||
}
|
||||
|
||||
tmp = VFS_TO_TMPFS(tdvp->v_mount);
|
||||
tdnode = VP_TO_TMPFS_DIR(tdvp);
|
||||
|
||||
/* If source and target are the same file, there is nothing to do. */
|
||||
if (fvp == tvp) {
|
||||
error = 0;
|
||||
@ -963,8 +1100,37 @@ tmpfs_rename(struct vop_rename_args *v)
|
||||
|
||||
/* If we need to move the directory between entries, lock the
|
||||
* source so that we can safely operate on it. */
|
||||
if (fdvp != tdvp && fdvp != tvp)
|
||||
vn_lock(fdvp, LK_EXCLUSIVE | LK_RETRY);
|
||||
if (fdvp != tdvp && fdvp != tvp) {
|
||||
if (vn_lock(fdvp, LK_EXCLUSIVE | LK_NOWAIT) != 0) {
|
||||
mp = tdvp->v_mount;
|
||||
error = vfs_busy(mp, 0);
|
||||
if (error != 0) {
|
||||
mp = NULL;
|
||||
goto out;
|
||||
}
|
||||
error = tmpfs_rename_relock(fdvp, &fvp, tdvp, &tvp,
|
||||
fcnp, tcnp);
|
||||
if (error != 0) {
|
||||
vfs_unbusy(mp);
|
||||
return (error);
|
||||
}
|
||||
ASSERT_VOP_ELOCKED(fdvp,
|
||||
"tmpfs_rename: fdvp not locked");
|
||||
ASSERT_VOP_ELOCKED(tdvp,
|
||||
"tmpfs_rename: tdvp not locked");
|
||||
if (tvp != NULL)
|
||||
ASSERT_VOP_ELOCKED(tvp,
|
||||
"tmpfs_rename: tvp not locked");
|
||||
if (fvp == tvp) {
|
||||
error = 0;
|
||||
goto out_locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmp = VFS_TO_TMPFS(tdvp->v_mount);
|
||||
tdnode = VP_TO_TMPFS_DIR(tdvp);
|
||||
tnode = (tvp == NULL) ? NULL : VP_TO_TMPFS_NODE(tvp);
|
||||
fdnode = VP_TO_TMPFS_DIR(fdvp);
|
||||
fnode = VP_TO_TMPFS_NODE(fvp);
|
||||
de = tmpfs_dir_lookup(fdnode, fnode, fcnp);
|
||||
@ -1158,6 +1324,9 @@ tmpfs_rename(struct vop_rename_args *v)
|
||||
vrele(fdvp);
|
||||
vrele(fvp);
|
||||
|
||||
if (mp != NULL)
|
||||
vfs_unbusy(mp);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user