freebsd-dev/sys/fs/smbfs/smbfs_vfsops.c
Ian Dowse 0864ef1e8a Change the second argument of vflush() to an integer that specifies
the number of references on the filesystem root vnode to be both
expected and released. Many filesystems hold an extra reference on
the filesystem root vnode, which must be accounted for when
determining if the filesystem is busy and then released if it isn't
busy. The old `skipvp' approach required individual filesystem
xxx_unmount functions to re-implement much of vflush()'s logic to
deal with the root vnode.

All 9 filesystems that hold an extra reference on the root vnode
got the logic wrong in the case of forced unmounts, so `umount -f'
would always fail if there were any extra root vnode references.
Fix this issue centrally in vflush(), now that we can.

This commit also fixes a vnode reference leak in devfs, which could
result in idle devfs filesystems that refuse to unmount.

Reviewed by:	phk, bp
2001-05-16 18:04:37 +00:00

501 lines
12 KiB
C

/*
* Copyright (c) 2000-2001, Boris Popov
* All rights reserved.
*
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Boris Popov.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* 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.
*
* $FreeBSD$
*/
#include "opt_netsmb.h"
#ifndef NETSMB
#error "SMBFS requires option NETSMB"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/bio.h>
#include <sys/buf.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/vnode.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/malloc.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_dev.h>
#include <fs/smbfs/smbfs.h>
#include <fs/smbfs/smbfs_node.h>
#include <fs/smbfs/smbfs_subr.h>
int smbfs_debuglevel = 0;
static int smbfs_version = SMBFS_VERSION;
#ifdef SMBFS_USEZONE
#include <vm/vm.h>
#include <vm/vm_extern.h>
#include <vm/vm_zone.h>
vm_zone_t smbfsmount_zone;
#endif
SYSCTL_NODE(_vfs, OID_AUTO, smbfs, CTLFLAG_RW, 0, "SMB/CIFS file system");
SYSCTL_INT(_vfs_smbfs, OID_AUTO, version, CTLFLAG_RD, &smbfs_version, 0, "");
SYSCTL_INT(_vfs_smbfs, OID_AUTO, debuglevel, CTLFLAG_RW, &smbfs_debuglevel, 0, "");
static MALLOC_DEFINE(M_SMBFSHASH, "SMBFS hash", "SMBFS hash table");
static int smbfs_mount(struct mount *, char *, caddr_t,
struct nameidata *, struct proc *);
static int smbfs_quotactl(struct mount *, int, uid_t, caddr_t, struct proc *);
static int smbfs_root(struct mount *, struct vnode **);
static int smbfs_start(struct mount *, int, struct proc *);
static int smbfs_statfs(struct mount *, struct statfs *, struct proc *);
static int smbfs_sync(struct mount *, int, struct ucred *, struct proc *);
static int smbfs_unmount(struct mount *, int, struct proc *);
static int smbfs_init(struct vfsconf *vfsp);
static int smbfs_uninit(struct vfsconf *vfsp);
#if __FreeBSD_version < 400009
static int smbfs_vget(struct mount *mp, ino_t ino, struct vnode **vpp);
static int smbfs_fhtovp(struct mount *, struct fid *,
struct sockaddr *, struct vnode **, int *,
struct ucred **);
static int smbfs_vptofh(struct vnode *, struct fid *);
#endif
static struct vfsops smbfs_vfsops = {
smbfs_mount,
smbfs_start,
smbfs_unmount,
smbfs_root,
smbfs_quotactl,
smbfs_statfs,
smbfs_sync,
#if __FreeBSD_version > 400008
vfs_stdvget,
vfs_stdfhtovp, /* shouldn't happen */
vfs_stdcheckexp,
vfs_stdvptofh, /* shouldn't happen */
#else
smbfs_vget,
smbfs_fhtovp,
smbfs_vptofh,
#endif
smbfs_init,
smbfs_uninit,
#ifndef FB_RELENG3
vfs_stdextattrctl,
#else
#define M_USE_RESERVE M_KERNEL
&sysctl___vfs_smbfs
#endif
};
VFS_SET(smbfs_vfsops, smbfs, VFCF_NETWORK);
MODULE_DEPEND(smbfs, netsmb, NSMB_VERSION, NSMB_VERSION, NSMB_VERSION);
MODULE_DEPEND(smbfs, libiconv, 1, 1, 1);
int smbfs_pbuf_freecnt = -1; /* start out unlimited */
static int
smbfs_mount(struct mount *mp, char *path, caddr_t data,
struct nameidata *ndp, struct proc *p)
{
struct smbfs_args args; /* will hold data from mount request */
struct smbmount *smp = NULL;
struct smb_vc *vcp;
struct smb_share *ssp = NULL;
struct vnode *vp;
struct smb_cred scred;
#ifndef FB_CURRENT
size_t size;
#endif
int error;
char *pc, *pe;
if (data == NULL) {
printf("missing data argument\n");
return EINVAL;
}
if (mp->mnt_flag & MNT_UPDATE) {
printf("MNT_UPDATE not implemented");
return EOPNOTSUPP;
}
error = copyin(data, (caddr_t)&args, sizeof(struct smbfs_args));
if (error)
return error;
if (args.version != SMBFS_VERSION) {
printf("mount version mismatch: kernel=%d, mount=%d\n",
SMBFS_VERSION, args.version);
return EINVAL;
}
smb_makescred(&scred, p, p->p_ucred);
error = smb_dev2share(args.dev, SMBM_EXEC, &scred, &ssp);
if (error) {
printf("invalid device handle %d (%d)\n", args.dev, error);
return error;
}
vcp = SSTOVC(ssp);
smb_share_unlock(ssp, 0, p);
mp->mnt_stat.f_iosize = SSTOVC(ssp)->vc_txmax;
#ifdef SMBFS_USEZONE
smp = zalloc(smbfsmount_zone);
#else
MALLOC(smp, struct smbmount*, sizeof(*smp), M_SMBFSDATA, M_USE_RESERVE);
#endif
if (smp == NULL) {
printf("could not alloc smbmount\n");
error = ENOMEM;
goto bad;
}
bzero(smp, sizeof(*smp));
mp->mnt_data = (qaddr_t)smp;
smp->sm_hash = hashinit(desiredvnodes, M_SMBFSHASH, &smp->sm_hashlen);
if (smp->sm_hash == NULL)
goto bad;
lockinit(&smp->sm_hashlock, PVFS, "smbfsh", 0, 0);
smp->sm_share = ssp;
smp->sm_root = NULL;
smp->sm_args = args;
smp->sm_caseopt = args.caseopt;
smp->sm_args.file_mode = (smp->sm_args.file_mode &
(S_IRWXU|S_IRWXG|S_IRWXO)) | S_IFREG;
smp->sm_args.dir_mode = (smp->sm_args.dir_mode &
(S_IRWXU|S_IRWXG|S_IRWXO)) | S_IFDIR;
/* simple_lock_init(&smp->sm_npslock);*/
#ifndef FB_CURRENT
error = copyinstr(path, mp->mnt_stat.f_mntonname, MNAMELEN - 1, &size);
if (error)
goto bad;
bzero(mp->mnt_stat.f_mntonname + size, MNAMELEN - size);
#endif
pc = mp->mnt_stat.f_mntfromname;
pe = pc + sizeof(mp->mnt_stat.f_mntfromname);
bzero(pc, MNAMELEN);
*pc++ = '/';
*pc++ = '/';
pc=index(strncpy(pc, vcp->vc_username, pe - pc - 2), 0);
if (pc < pe-1) {
*(pc++) = '@';
pc = index(strncpy(pc, vcp->vc_srvname, pe - pc - 2), 0);
if (pc < pe - 1) {
*(pc++) = '/';
strncpy(pc, ssp->ss_name, pe - pc - 2);
}
}
/* protect against invalid mount points */
smp->sm_args.mount_point[sizeof(smp->sm_args.mount_point) - 1] = '\0';
vfs_getnewfsid(mp);
error = smbfs_root(mp, &vp);
if (error)
goto bad;
VOP_UNLOCK(vp, 0, p);
SMBVDEBUG("root.v_usecount = %d\n", vp->v_usecount);
#ifdef DIAGNOSTICS
SMBERROR("mp=%p\n", mp);
#endif
return error;
bad:
if (smp) {
if (smp->sm_hash)
free(smp->sm_hash, M_SMBFSHASH);
lockdestroy(&smp->sm_hashlock);
#ifdef SMBFS_USEZONE
zfree(smbfsmount_zone, smp);
#else
free(smp, M_SMBFSDATA);
#endif
}
if (ssp)
smb_share_put(ssp, &scred);
return error;
}
/* Unmount the filesystem described by mp. */
static int
smbfs_unmount(struct mount *mp, int mntflags, struct proc *p)
{
struct smbmount *smp = VFSTOSMBFS(mp);
struct smb_cred scred;
int error, flags;
SMBVDEBUG("smbfs_unmount: flags=%04x\n", mntflags);
flags = 0;
if (mntflags & MNT_FORCE)
flags |= FORCECLOSE;
/* There is 1 extra root vnode reference from smbfs_mount(). */
error = vflush(mp, 1, flags);
if (error)
return error;
smb_makescred(&scred, p, p->p_ucred);
smb_share_put(smp->sm_share, &scred);
mp->mnt_data = (qaddr_t)0;
if (smp->sm_hash)
free(smp->sm_hash, M_SMBFSHASH);
lockdestroy(&smp->sm_hashlock);
#ifdef SMBFS_USEZONE
zfree(smbfsmount_zone, smp);
#else
free(smp, M_SMBFSDATA);
#endif
mp->mnt_flag &= ~MNT_LOCAL;
return error;
}
/*
* Return locked root vnode of a filesystem
*/
static int
smbfs_root(struct mount *mp, struct vnode **vpp)
{
struct smbmount *smp = VFSTOSMBFS(mp);
struct vnode *vp;
struct smbnode *np;
struct smbfattr fattr;
struct proc *p = curproc;
struct ucred *cred = p->p_ucred;
struct smb_cred scred;
int error;
if (smp == NULL) {
SMBERROR("smp == NULL (bug in umount)\n");
return EINVAL;
}
if (smp->sm_root) {
*vpp = SMBTOV(smp->sm_root);
return vget(*vpp, LK_EXCLUSIVE | LK_RETRY, p);
}
smb_makescred(&scred, p, cred);
error = smbfs_smb_lookup(NULL, NULL, 0, &fattr, &scred);
if (error)
return error;
error = smbfs_nget(mp, NULL, "TheRooT", 7, &fattr, &vp);
if (error)
return error;
vp->v_flag |= VROOT;
np = VTOSMB(vp);
smp->sm_root = np;
*vpp = vp;
return 0;
}
/*
* Vfs start routine, a no-op.
*/
/* ARGSUSED */
static int
smbfs_start(mp, flags, p)
struct mount *mp;
int flags;
struct proc *p;
{
SMBVDEBUG("flags=%04x\n", flags);
return 0;
}
/*
* Do operations associated with quotas, not supported
*/
/* ARGSUSED */
static int
smbfs_quotactl(mp, cmd, uid, arg, p)
struct mount *mp;
int cmd;
uid_t uid;
caddr_t arg;
struct proc *p;
{
SMBVDEBUG("return EOPNOTSUPP\n");
return EOPNOTSUPP;
}
/*ARGSUSED*/
int
smbfs_init(struct vfsconf *vfsp)
{
#ifndef SMP
int name[2];
int olen, ncpu, plen, error;
name[0] = CTL_HW;
name[1] = HW_NCPU;
error = kernel_sysctl(curproc, name, 2, &ncpu, &olen, NULL, 0, &plen);
if (error == 0 && ncpu > 1)
printf("warning: smbfs module compiled without SMP support.");
#endif
#ifdef SMBFS_USEZONE
smbfsmount_zone = zinit("SMBFSMOUNT", sizeof(struct smbmount), 0, 0, 1);
#endif
smbfs_pbuf_freecnt = nswbuf / 2 + 1;
SMBVDEBUG("done.\n");
return 0;
}
/*ARGSUSED*/
int
smbfs_uninit(struct vfsconf *vfsp)
{
SMBVDEBUG("done.\n");
return 0;
}
/*
* smbfs_statfs call
*/
int
smbfs_statfs(struct mount *mp, struct statfs *sbp, struct proc *p)
{
struct smbmount *smp = VFSTOSMBFS(mp);
struct smbnode *np = smp->sm_root;
struct smb_share *ssp = smp->sm_share;
struct smb_cred scred;
int error = 0;
if (np == NULL)
return EINVAL;
sbp->f_iosize = SSTOVC(ssp)->vc_txmax; /* optimal transfer block size */
sbp->f_spare2 = 0; /* placeholder */
smb_makescred(&scred, p, p->p_ucred);
if (SMB_DIALECT(SSTOVC(ssp)) >= SMB_DIALECT_LANMAN2_0)
error = smbfs_smb_statfs2(ssp, sbp, &scred);
else
error = smbfs_smb_statfs(ssp, sbp, &scred);
if (error)
return error;
sbp->f_flags = 0; /* copy of mount exported flags */
if (sbp != &mp->mnt_stat) {
sbp->f_fsid = mp->mnt_stat.f_fsid; /* file system id */
sbp->f_owner = mp->mnt_stat.f_owner; /* user that mounted the filesystem */
sbp->f_type = mp->mnt_vfc->vfc_typenum; /* type of filesystem */
bcopy(mp->mnt_stat.f_mntonname, sbp->f_mntonname, MNAMELEN);
bcopy(mp->mnt_stat.f_mntfromname, sbp->f_mntfromname, MNAMELEN);
}
strncpy(sbp->f_fstypename, mp->mnt_vfc->vfc_name, MFSNAMELEN);
return 0;
}
/*
* Flush out the buffer cache
*/
/* ARGSUSED */
static int
smbfs_sync(mp, waitfor, cred, p)
struct mount *mp;
int waitfor;
struct ucred *cred;
struct proc *p;
{
struct vnode *vp;
int error, allerror = 0;
/*
* Force stale buffer cache information to be flushed.
*/
loop:
for (vp = mp->mnt_vnodelist.lh_first;
vp != NULL;
vp = vp->v_mntvnodes.le_next) {
/*
* If the vnode that we are about to sync is no longer
* associated with this mount point, start over.
*/
if (vp->v_mount != mp)
goto loop;
#ifndef FB_RELENG3
if (VOP_ISLOCKED(vp, NULL) || TAILQ_EMPTY(&vp->v_dirtyblkhd) ||
#else
if (VOP_ISLOCKED(vp) || TAILQ_EMPTY(&vp->v_dirtyblkhd) ||
#endif
waitfor == MNT_LAZY)
continue;
if (vget(vp, LK_EXCLUSIVE, p))
goto loop;
error = VOP_FSYNC(vp, cred, waitfor, p);
if (error)
allerror = error;
vput(vp);
}
return (allerror);
}
#if __FreeBSD_version < 400009
/*
* smbfs flat namespace lookup. Unsupported.
*/
/* ARGSUSED */
static int smbfs_vget(mp, ino, vpp)
struct mount *mp;
ino_t ino;
struct vnode **vpp;
{
return (EOPNOTSUPP);
}
/* ARGSUSED */
static int smbfs_fhtovp(mp, fhp, nam, vpp, exflagsp, credanonp)
struct mount *mp;
struct fid *fhp;
struct sockaddr *nam;
struct vnode **vpp;
int *exflagsp;
struct ucred **credanonp;
{
return (EINVAL);
}
/*
* Vnode pointer to File handle, should never happen either
*/
/* ARGSUSED */
static int
smbfs_vptofh(vp, fhp)
struct vnode *vp;
struct fid *fhp;
{
return (EINVAL);
}
#endif /* __FreeBSD_version < 400009 */