summaryrefslogtreecommitdiffstats
path: root/sys/kern/vfs_extattr.c
diff options
context:
space:
mode:
authoriedowse <iedowse@FreeBSD.org>2001-02-28 20:54:28 +0000
committeriedowse <iedowse@FreeBSD.org>2001-02-28 20:54:28 +0000
commit63324ae54624fc9c3b71a42fc13103a72c73dd8f (patch)
tree60075844c6911fc6d502963c2a78b7e171aa4567 /sys/kern/vfs_extattr.c
parent3af5c788c31cdf5c004daba60a9c823d9869821b (diff)
downloadFreeBSD-src-63324ae54624fc9c3b71a42fc13103a72c73dd8f.zip
FreeBSD-src-63324ae54624fc9c3b71a42fc13103a72c73dd8f.tar.gz
The kernel did not hold a vnode reference associated with the
`rootvnode' pointer, but vfs_syscalls.c's checkdirs() assumed that it did. This bug reliably caused a panic at reboot time if any filesystem had been mounted directly over /. The checkdirs() function is called at mount time to find any process fd_cdir or fd_rdir pointers referencing the covered mountpoint vnode. It transfers these to point at the root of the new filesystem. However, this process was not reversed at unmount time, so processes with a cwd/root at a mount point would unexpectedly lose their cwd/root following a mount-unmount cycle at that mountpoint. This change should fix both of the above issues. Start_init() now holds an extra vnode reference corresponding to `rootvnode', and dounmount() releases this reference when the root filesystem is unmounted just before reboot. Dounmount() now undoes the actions taken by checkdirs() at mount time; any process cdir/rdir pointers that reference the root vnode of the unmounted filesystem are transferred to the now-uncovered vnode. Reviewed by: bde, phk
Diffstat (limited to 'sys/kern/vfs_extattr.c')
-rw-r--r--sys/kern/vfs_extattr.c43
1 files changed, 32 insertions, 11 deletions
diff --git a/sys/kern/vfs_extattr.c b/sys/kern/vfs_extattr.c
index 0651257..8f95653 100644
--- a/sys/kern/vfs_extattr.c
+++ b/sys/kern/vfs_extattr.c
@@ -75,7 +75,7 @@
#include <vm/vm_page.h>
static int change_dir __P((struct nameidata *ndp, struct proc *p));
-static void checkdirs __P((struct vnode *olddp));
+static void checkdirs __P((struct vnode *olddp, struct vnode *newdp));
static int chroot_refuse_vdir_fds __P((struct filedesc *fdp));
static int getutimes __P((const struct timeval *, struct timespec *));
static int setfown __P((struct proc *, struct vnode *, uid_t, gid_t));
@@ -332,6 +332,8 @@ update:
*/
cache_purge(vp);
if (!error) {
+ struct vnode *newdp;
+
mtx_lock(&vp->v_interlock);
vp->v_flag &= ~VMOUNT;
vp->v_mountedhere = mp;
@@ -339,7 +341,10 @@ update:
mtx_lock(&mountlist_mtx);
TAILQ_INSERT_TAIL(&mountlist, mp, mnt_list);
mtx_unlock(&mountlist_mtx);
- checkdirs(vp);
+ if (VFS_ROOT(mp, &newdp))
+ panic("mount: lost mount");
+ checkdirs(vp, newdp);
+ vput(newdp);
VOP_UNLOCK(vp, 0, p);
if ((mp->mnt_flag & MNT_RDONLY) == 0)
error = vfs_allocate_syncvnode(mp);
@@ -360,21 +365,18 @@ update:
/*
* Scan all active processes to see if any of them have a current
- * or root directory onto which the new filesystem has just been
- * mounted. If so, replace them with the new mount point.
+ * or root directory of `olddp'. If so, replace them with the new
+ * mount point.
*/
static void
-checkdirs(olddp)
- struct vnode *olddp;
+checkdirs(olddp, newdp)
+ struct vnode *olddp, *newdp;
{
struct filedesc *fdp;
- struct vnode *newdp;
struct proc *p;
if (olddp->v_usecount == 1)
return;
- if (VFS_ROOT(olddp->v_mountedhere, &newdp))
- panic("mount: lost mount");
ALLPROC_LOCK(AP_SHARED);
LIST_FOREACH(p, &allproc, p_list) {
fdp = p->p_fd;
@@ -395,7 +397,6 @@ checkdirs(olddp)
VREF(newdp);
rootvnode = newdp;
}
- vput(newdp);
}
/*
@@ -470,7 +471,7 @@ dounmount(mp, flags, p)
int flags;
struct proc *p;
{
- struct vnode *coveredvp;
+ struct vnode *coveredvp, *fsrootvp;
int error;
int async_flag;
@@ -488,6 +489,16 @@ dounmount(mp, flags, p)
cache_purgevfs(mp); /* remove cache entries for this file sys */
if (mp->mnt_syncer != NULL)
vrele(mp->mnt_syncer);
+ /* Move process cdir/rdir refs on fs root to underlying vnode. */
+ if (VFS_ROOT(mp, &fsrootvp) == 0) {
+ if (mp->mnt_vnodecovered != NULL)
+ checkdirs(fsrootvp, mp->mnt_vnodecovered);
+ if (fsrootvp == rootvnode) {
+ vrele(rootvnode);
+ rootvnode = NULL;
+ }
+ vput(fsrootvp);
+ }
if (((mp->mnt_flag & MNT_RDONLY) ||
(error = VFS_SYNC(mp, MNT_WAIT, p->p_ucred, p)) == 0) ||
(flags & MNT_FORCE)) {
@@ -496,6 +507,16 @@ dounmount(mp, flags, p)
vn_finished_write(mp);
mtx_lock(&mountlist_mtx);
if (error) {
+ /* Undo cdir/rdir and rootvnode changes made above. */
+ if (VFS_ROOT(mp, &fsrootvp) == 0) {
+ if (mp->mnt_vnodecovered != NULL)
+ checkdirs(mp->mnt_vnodecovered, fsrootvp);
+ if (rootvnode == NULL) {
+ rootvnode = fsrootvp;
+ vref(rootvnode);
+ }
+ vput(fsrootvp);
+ }
if ((mp->mnt_flag & MNT_RDONLY) == 0 && mp->mnt_syncer == NULL)
(void) vfs_allocate_syncvnode(mp);
mp->mnt_kern_flag &= ~MNTK_UNMOUNT;
OpenPOWER on IntegriCloud