Index: vfs_mount.c =================================================================== RCS file: /cvs/src/sys/kern/vfs_mount.c,v retrieving revision 1.143 diff -u -r1.143 vfs_mount.c --- vfs_mount.c 5 Oct 2004 11:26:43 -0000 1.143 +++ vfs_mount.c 17 Oct 2004 20:34:23 -0000 @@ -108,6 +108,14 @@ static int vfs_donmount(struct thread *td, int fsflags, struct uio *fsoptions); +/* A vessel for temporary storage used while mounting a filesystem */ +struct mntscratch { + struct vnode *vp; /* vnode to be covered */ + struct mount *mp; /* newly created mount point */ + int flag; /* saved mnt_flag, in case of aborted update */ + int kern_flag; /* saved mnt_kern_flag */ +}; + static int usermount = 0; SYSCTL_INT(_vfs, OID_AUTO, usermount, CTLFLAG_RW, &usermount, 0, "Unprivileged users may mount and unmount file systems"); @@ -647,148 +655,83 @@ } /* - * vfs_domount(): actually attempt a filesystem mount. + * Initialize the struct mount for this filesystem. This occurs + * before VFS_[O]MOUNT() is called. If successful, we return with + * msp->mp set to a busied mount point, and with VI_MOUNT set on the + * vnode to be covered. */ static int -vfs_domount( - struct thread *td, /* Flags common to all filesystems. */ - const char *fstype, /* Filesystem type. */ - char *fspath, /* Mount path. */ - int fsflags, /* Flags common to all filesystems. */ - void *fsdata, /* Options local to the filesystem. */ - int compat /* Invocation from compat syscall. */ - ) +vfs_startmount(struct thread *td, struct mntscratch *msp, const char *fstype, + char *fspath, int fsflags, void *fsdata, int compat) { + struct vattr va; linker_file_t lf; - struct vnode *vp; - struct mount *mp; struct vfsconf *vfsp; - int error, flag = 0, kern_flag = 0; - struct vattr va; - struct nameidata nd; - - /* - * Be ultra-paranoid about making sure the type and fspath - * variables will fit in our mp buffers, including the - * terminating NUL. - */ - if (strlen(fstype) >= MFSNAMELEN || strlen(fspath) >= MNAMELEN) - return (ENAMETOOLONG); - - if (jailed(td->td_ucred)) - return (EPERM); - if (usermount == 0) { - if ((error = suser(td)) != 0) - return (error); - } + int error; - /* - * Do not allow NFS export or MNT_SUIDDIR by unprivileged users. - */ - if (fsflags & (MNT_EXPORTED | MNT_SUIDDIR)) { - if ((error = suser(td)) != 0) - return (error); - } - /* - * Silently enforce MNT_NODEV, MNT_NOSUID and MNT_USER for - * unprivileged users. - */ - if (suser(td) != 0) - fsflags |= MNT_NODEV | MNT_NOSUID | MNT_USER; - /* - * Get vnode to be covered - */ - NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, fspath, td); - if ((error = namei(&nd)) != 0) - return (error); - NDFREE(&nd, NDF_ONLY_PNBUF); - vp = nd.ni_vp; if (fsflags & MNT_UPDATE) { - if ((vp->v_vflag & VV_ROOT) == 0) { - vput(vp); + if ((msp->vp->v_vflag & VV_ROOT) == 0) return (EINVAL); - } - mp = vp->v_mount; - flag = mp->mnt_flag; - kern_flag = mp->mnt_kern_flag; + msp->mp = msp->vp->v_mount; + msp->flag = msp->mp->mnt_flag; + msp->kern_flag = msp->mp->mnt_kern_flag; /* * We only allow the filesystem to be reloaded if it * is currently mounted read-only. */ if ((fsflags & MNT_RELOAD) && - ((mp->mnt_flag & MNT_RDONLY) == 0)) { - vput(vp); + ((msp->mp->mnt_flag & MNT_RDONLY) == 0)) return (EOPNOTSUPP); /* Needs translation */ - } /* * Only privileged root, or (if MNT_USER is set) the user that * did the original mount is permitted to update it. */ - error = vfs_suser(mp, td); - if (error) { - vput(vp); + error = vfs_suser(msp->mp, td); + if (error) return (error); - } - if (vfs_busy(mp, LK_NOWAIT, 0, td)) { - vput(vp); + if (vfs_busy(msp->mp, LK_NOWAIT, 0, td)) return (EBUSY); - } - VI_LOCK(vp); - if ((vp->v_iflag & VI_MOUNT) != 0 || - vp->v_mountedhere != NULL) { - VI_UNLOCK(vp); - vfs_unbusy(mp, td); - vput(vp); + VI_LOCK(msp->vp); + if ((msp->vp->v_iflag & VI_MOUNT) != 0 || + msp->vp->v_mountedhere != NULL) { + VI_UNLOCK(msp->vp); + vfs_unbusy(msp->mp, td); return (EBUSY); } - vp->v_iflag |= VI_MOUNT; - VI_UNLOCK(vp); - mp->mnt_flag |= fsflags & + msp->vp->v_iflag |= VI_MOUNT; + VI_UNLOCK(msp->vp); + msp->mp->mnt_flag |= fsflags & (MNT_RELOAD | MNT_FORCE | MNT_UPDATE | MNT_SNAPSHOT); - VOP_UNLOCK(vp, 0, td); if (compat == 0) { - mp->mnt_optnew = fsdata; - vfs_mergeopts(mp->mnt_optnew, mp->mnt_opt); + msp->mp->mnt_optnew = fsdata; + vfs_mergeopts(msp->mp->mnt_optnew, msp->mp->mnt_opt); } } else { /* * If the user is not root, ensure that they own the directory * onto which we are attempting to mount. */ - error = VOP_GETATTR(vp, &va, td->td_ucred, td); - if (error) { - vput(vp); + error = VOP_GETATTR(msp->vp, &va, td->td_ucred, td); + if (error) return (error); - } if (va.va_uid != td->td_ucred->cr_uid) { - if ((error = suser(td)) != 0) { - vput(vp); + if ((error = suser(td)) != 0) return (error); - } } - if ((error = vinvalbuf(vp, V_SAVE, td->td_ucred, td, 0, 0)) != 0) { - vput(vp); + if ((error = vinvalbuf(msp->vp, V_SAVE, td->td_ucred, td, 0, 0)) != 0) return (error); - } - if (vp->v_type != VDIR) { - vput(vp); + if (msp->vp->v_type != VDIR) return (ENOTDIR); - } vfsp = vfs_byname(fstype); if (vfsp == NULL) { /* Only load modules for root (very important!). */ - if ((error = suser(td)) != 0) { - vput(vp); + if ((error = suser(td)) != 0) return (error); - } error = securelevel_gt(td->td_ucred, 0); - if (error) { - vput(vp); + if (error) return (error); - } error = linker_load_module(NULL, fstype, NULL, NULL, &lf); if (error || lf == NULL) { - vput(vp); if (lf == NULL) error = ENODEV; return (error); @@ -799,49 +742,214 @@ if (vfsp == NULL) { lf->userrefs--; linker_file_unload(lf, LINKER_UNLOAD_FORCE); - vput(vp); return (ENODEV); } } - VI_LOCK(vp); - if ((vp->v_iflag & VI_MOUNT) != 0 || - vp->v_mountedhere != NULL) { - VI_UNLOCK(vp); - vput(vp); + VI_LOCK(msp->vp); + if ((msp->vp->v_iflag & VI_MOUNT) != 0 || + msp->vp->v_mountedhere != NULL) { + VI_UNLOCK(msp->vp); return (EBUSY); } - vp->v_iflag |= VI_MOUNT; - VI_UNLOCK(vp); + msp->vp->v_iflag |= VI_MOUNT; + VI_UNLOCK(msp->vp); - /* - * Allocate and initialize the filesystem. - */ - error = vfs_mount_alloc(vp, vfsp, fspath, td, &mp); - if (error) { - vput(vp); + /* Allocate and initialize the filesystem. */ + error = vfs_mount_alloc(msp->vp, vfsp, fspath, td, &msp->mp); + if (error) return (error); - } - VOP_UNLOCK(vp, 0, td); /* XXXMAC: pass to vfs_mount_alloc? */ if (compat == 0) - mp->mnt_optnew = fsdata; + msp->mp->mnt_optnew = fsdata; } + return (0); +} + +/* + * Undo the actions of vfs_startmount() when we need to abort. + */ +static void +vfs_abortmount(struct thread *td, struct mntscratch *msp) +{ + + if (msp->mp->mnt_flag & MNT_UPDATE) { + msp->mp->mnt_flag = msp->flag; + msp->mp->mnt_kern_flag = msp->kern_flag; + } + VI_LOCK(msp->vp); + msp->vp->v_iflag &= ~VI_MOUNT; + VI_UNLOCK(msp->vp); + if (msp->mp->mnt_flag & MNT_UPDATE) + vfs_unbusy(msp->mp, td); + else + vfs_mount_destroy(msp->mp, td); +} + +/* + * Complete an update to a mount point after VFS_[O]MOUNT() has been + * called. If an error occurs, try to revert the mount to its + * previous (consistent) state. + */ +static int +vfs_finishupdate(struct thread *td, struct mntscratch *msp) +{ + int error = 0; + + if (msp->mp->mnt_kern_flag & MNTK_WANTRDWR) + msp->mp->mnt_flag &= ~MNT_RDONLY; + msp->mp->mnt_flag &= + ~(MNT_UPDATE | MNT_RELOAD | MNT_FORCE | MNT_SNAPSHOT); + msp->mp->mnt_kern_flag &= ~MNTK_WANTRDWR; + if ((msp->mp->mnt_flag & MNT_RDONLY) == 0) { + if (msp->mp->mnt_syncer == NULL) + error = vfs_allocate_syncvnode(msp->mp); + } else { + if (msp->mp->mnt_syncer != NULL) + vrele(msp->mp->mnt_syncer); + msp->mp->mnt_syncer = NULL; + } + if (error) { + msp->mp->mnt_flag = msp->flag; + msp->mp->mnt_kern_flag = msp->kern_flag; + /* + * XXX We would need to call VFS_[O]MOUNT() here to restore + * the previous set of flags, but currently, + * vfs_allocate_syncvnode() never fails anyway. + */ + } + VI_LOCK(msp->vp); + msp->vp->v_iflag &= ~VI_MOUNT; + VI_UNLOCK(msp->vp); + vfs_unbusy(msp->mp, td); + return (error); +} + +/* + * Complete the bookkeeping necessary to add a new mount point after + * VFS_[O]MOUNT() has been called but before VFS_START(). If an error + * occurs, we call VFS_UNMOUNT() and undo the actions of vfs_startmount(). + */ +static int +vfs_finishmount(struct thread *td, struct mntscratch *msp) +{ + struct vnode *newdp; + int error; + + VI_LOCK(msp->vp); + msp->vp->v_iflag &= ~VI_MOUNT; + VI_UNLOCK(msp->vp); + msp->vp->v_mountedhere = msp->mp; + mtx_lock(&mountlist_mtx); + TAILQ_INSERT_TAIL(&mountlist, msp->mp, mnt_list); + mtx_unlock(&mountlist_mtx); + error = VFS_ROOT(msp->mp, &newdp, td); + if (error) { + /* + * XXX Could anything bad have happened while we were on + * the mount list? + */ + if (VFS_UNMOUNT(msp->mp, MNT_FORCE, td) != 0) { + /* + * XXX Unfortunately, an error return does not tell us + * whether the unmount operation got far enough to free + * FS-specific data structures or not. However, most + * local filesystems will clear MNT_LOCAL if they have. + * This needs to be revisited. + * + * If we can't complete the mount operation and we + * can't forcibly unmount, then we're just screwed. + */ + if (msp->mp->mnt_flag & MNT_LOCAL) + panic("vfs_finishmount: unable to abort"); + } + mtx_lock(&mountlist_mtx); + TAILQ_REMOVE(&mountlist, msp->mp, mnt_list); + mtx_unlock(&mountlist_mtx); + msp->vp->v_mountedhere = NULL; + vfs_mount_destroy(msp->mp, td); + return (error); + } + vfs_event_signal(NULL, VQ_MOUNT, 0); + checkdirs(msp->vp, newdp); + vput(newdp); + if ((msp->mp->mnt_flag & MNT_RDONLY) == 0) + error = vfs_allocate_syncvnode(msp->mp); + vfs_unbusy(msp->mp, td); + return (0); +} + +/* + * vfs_domount(): actually attempt a filesystem mount. + */ +static int +vfs_domount( + struct thread *td, /* Flags common to all filesystems. */ + const char *fstype, /* Filesystem type. */ + char *fspath, /* Mount path. */ + int fsflags, /* Flags common to all filesystems. */ + void *fsdata, /* Options local to the filesystem. */ + int compat /* Invocation from compat syscall. */ + ) +{ + struct mntscratch ms; + struct nameidata nd; + int error; + + /* + * Be ultra-paranoid about making sure the type and fspath + * variables will fit in our mp buffers, including the + * terminating NUL. + */ + if (strlen(fstype) >= MFSNAMELEN || strlen(fspath) >= MNAMELEN) + return (ENAMETOOLONG); + + if (jailed(td->td_ucred)) + return (EPERM); + if (usermount == 0) { + if ((error = suser(td)) != 0) + return (error); + } + + /* + * Do not allow NFS export or MNT_SUIDDIR by unprivileged users. + */ + if (fsflags & (MNT_EXPORTED | MNT_SUIDDIR)) { + if ((error = suser(td)) != 0) + return (error); + } + /* + * Silently enforce MNT_NODEV, MNT_NOSUID and MNT_USER for + * unprivileged users. + */ + if (suser(td) != 0) + fsflags |= MNT_NODEV | MNT_NOSUID | MNT_USER; + /* + * Get vnode to be covered + */ + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, fspath, td); + if ((error = namei(&nd)) != 0) + return (error); + NDFREE(&nd, NDF_ONLY_PNBUF); + ms.vp = nd.ni_vp; + + error = vfs_startmount(td, &ms, fstype, fspath, fsflags, fsdata, + compat); + if (error) { + vput(ms.vp); + return (error); + } + VOP_UNLOCK(ms.vp, 0, td); + /* * Check if the fs implements the type VFS_[O]MOUNT() * function we are looking for. */ - if ((compat == 0) == (mp->mnt_op->vfs_omount != NULL)) { + if ((compat == 0) == (ms.mp->mnt_op->vfs_omount != NULL)) { printf("%s doesn't support the %s mount syscall\n", - mp->mnt_vfc->vfc_name, compat ? "old" : "new"); - VI_LOCK(vp); - vp->v_iflag &= ~VI_MOUNT; - VI_UNLOCK(vp); - if (mp->mnt_flag & MNT_UPDATE) - vfs_unbusy(mp, td); - else - vfs_mount_destroy(mp, td); - vrele(vp); + ms.mp->mnt_vfc->vfc_name, compat ? "old" : "new"); + vfs_abortmount(td, &ms); + vrele(ms.vp); return (EOPNOTSUPP); } @@ -849,88 +957,55 @@ * Set the mount level flags. */ if (fsflags & MNT_RDONLY) - mp->mnt_flag |= MNT_RDONLY; - else if (mp->mnt_flag & MNT_RDONLY) - mp->mnt_kern_flag |= MNTK_WANTRDWR; - mp->mnt_flag &=~ MNT_UPDATEMASK; - mp->mnt_flag |= fsflags & (MNT_UPDATEMASK | MNT_FORCE); + ms.mp->mnt_flag |= MNT_RDONLY; + else if (ms.mp->mnt_flag & MNT_RDONLY) + ms.mp->mnt_kern_flag |= MNTK_WANTRDWR; + ms.mp->mnt_flag &=~ MNT_UPDATEMASK; + ms.mp->mnt_flag |= fsflags & (MNT_UPDATEMASK | MNT_FORCE); /* * Mount the filesystem. * XXX The final recipients of VFS_MOUNT just overwrite the ndp they * get. No freeing of cn_pnbuf. */ if (compat) - error = VFS_OMOUNT(mp, fspath, fsdata, td); + error = VFS_OMOUNT(ms.mp, fspath, fsdata, td); else - error = VFS_MOUNT(mp, td); + error = VFS_MOUNT(ms.mp, td); if (!error) { - if (mp->mnt_opt != NULL) - vfs_freeopts(mp->mnt_opt); - mp->mnt_opt = mp->mnt_optnew; + if (ms.mp->mnt_opt != NULL) + vfs_freeopts(ms.mp->mnt_opt); + ms.mp->mnt_opt = ms.mp->mnt_optnew; } - /* - * Prevent external consumers of mount options from reading - * mnt_optnew. - */ - mp->mnt_optnew = NULL; - if (mp->mnt_flag & MNT_UPDATE) { - if (mp->mnt_kern_flag & MNTK_WANTRDWR) - mp->mnt_flag &= ~MNT_RDONLY; - mp->mnt_flag &= - ~(MNT_UPDATE | MNT_RELOAD | MNT_FORCE | MNT_SNAPSHOT); - mp->mnt_kern_flag &= ~MNTK_WANTRDWR; - if (error) { - mp->mnt_flag = flag; - mp->mnt_kern_flag = kern_flag; - } - if ((mp->mnt_flag & MNT_RDONLY) == 0) { - if (mp->mnt_syncer == NULL) - error = vfs_allocate_syncvnode(mp); - } else { - if (mp->mnt_syncer != NULL) - vrele(mp->mnt_syncer); - mp->mnt_syncer = NULL; - } - vfs_unbusy(mp, td); - VI_LOCK(vp); - vp->v_iflag &= ~VI_MOUNT; - VI_UNLOCK(vp); - vrele(vp); + /* Prevent external consumers from reading mnt_optnew. */ + ms.mp->mnt_optnew = NULL; + + if (ms.mp->mnt_flag & MNT_UPDATE) { + if (error) + vfs_abortmount(td, &ms); + else + error = vfs_finishupdate(td, &ms); + vrele(ms.vp); return (error); } - vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td); + + vn_lock(ms.vp, LK_EXCLUSIVE | LK_RETRY, td); /* * Put the new filesystem on the mount list after root. */ - cache_purge(vp); - if (!error) { - struct vnode *newdp; - - VI_LOCK(vp); - vp->v_iflag &= ~VI_MOUNT; - VI_UNLOCK(vp); - vp->v_mountedhere = mp; - mtx_lock(&mountlist_mtx); - TAILQ_INSERT_TAIL(&mountlist, mp, mnt_list); - mtx_unlock(&mountlist_mtx); - vfs_event_signal(NULL, VQ_MOUNT, 0); - if (VFS_ROOT(mp, &newdp, td)) - panic("mount: lost mount"); - checkdirs(vp, newdp); - vput(newdp); - VOP_UNLOCK(vp, 0, td); - if ((mp->mnt_flag & MNT_RDONLY) == 0) - error = vfs_allocate_syncvnode(mp); - vfs_unbusy(mp, td); - if (error || (error = VFS_START(mp, 0, td)) != 0) - vrele(vp); - } else { - VI_LOCK(vp); - vp->v_iflag &= ~VI_MOUNT; - VI_UNLOCK(vp); - vfs_mount_destroy(mp, td); - vput(vp); + cache_purge(ms.vp); + if (error) { + vfs_abortmount(td, &ms); + vput(ms.vp); + return (error); + } + error = vfs_finishmount(td, &ms); + if (!error) + error = VFS_START(ms.mp, 0, td); + if (error) { + vput(ms.vp); + return (error); } + VOP_UNLOCK(ms.vp, 0, td); return (error); }