#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/spinlock2.h>
#include <sys/eventhandler.h>
#include <sys/kthread.h>
#include <sys/sysctl.h>
#include <machine/limits.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
struct mountscan_info {
TAILQ_ENTRY(mountscan_info) msi_entry;
int msi_how;
struct mount *msi_node;
};
struct vmntvnodescan_info {
TAILQ_ENTRY(vmntvnodescan_info) entry;
struct vnode *vp;
};
static int
mount_cmp(struct mount *mnt1, struct mount *mnt2)
{
if (mnt1->mnt_stat.f_fsid.val[0] < mnt2->mnt_stat.f_fsid.val[0])
return -1;
if (mnt1->mnt_stat.f_fsid.val[0] > mnt2->mnt_stat.f_fsid.val[0])
return 1;
if (mnt1->mnt_stat.f_fsid.val[1] < mnt2->mnt_stat.f_fsid.val[1])
return -1;
if (mnt1->mnt_stat.f_fsid.val[1] > mnt2->mnt_stat.f_fsid.val[1])
return 1;
return 0;
}
static int
mount_fsid_cmp(fsid_t *fsid, struct mount *mnt)
{
if (fsid->val[0] < mnt->mnt_stat.f_fsid.val[0])
return -1;
if (fsid->val[0] > mnt->mnt_stat.f_fsid.val[0])
return 1;
if (fsid->val[1] < mnt->mnt_stat.f_fsid.val[1])
return -1;
if (fsid->val[1] > mnt->mnt_stat.f_fsid.val[1])
return 1;
return 0;
}
RB_HEAD(mount_rb_tree, mount);
RB_PROTOTYPEX(mount_rb_tree, FSID, mount, mnt_node, mount_cmp, fsid_t *);
RB_GENERATE(mount_rb_tree, mount, mnt_node, mount_cmp);
RB_GENERATE_XLOOKUP(mount_rb_tree, FSID, mount, mnt_node,
mount_fsid_cmp, fsid_t *);
static int vnlru_nowhere = 0;
SYSCTL_INT(_debug, OID_AUTO, vnlru_nowhere, CTLFLAG_RD,
&vnlru_nowhere, 0,
"Number of times the vnlru process ran without success");
static struct lwkt_token mntid_token;
static struct mount dummymount;
struct mntlist mountlist = TAILQ_HEAD_INITIALIZER(mountlist);
struct mount_rb_tree mounttree = RB_INITIALIZER(dev_tree_mounttree);
static TAILQ_HEAD(,mountscan_info) mountscan_list;
static struct lwkt_token mountlist_token;
static TAILQ_HEAD(,bio_ops) bio_ops_list = TAILQ_HEAD_INITIALIZER(bio_ops_list);
void
vfs_mount_init(void)
{
lwkt_token_init(&mountlist_token, "mntlist");
lwkt_token_init(&mntid_token, "mntid");
TAILQ_INIT(&mountscan_list);
mount_init(&dummymount, NULL);
dummymount.mnt_flag |= MNT_RDONLY;
dummymount.mnt_kern_flag |= MNTK_ALL_MPSAFE;
}
static void
vremovevnodemnt(struct vnode *vp)
{
struct vmntvnodescan_info *info;
struct mount *mp = vp->v_mount;
TAILQ_FOREACH(info, &mp->mnt_vnodescan_list, entry) {
if (info->vp == vp)
info->vp = TAILQ_NEXT(vp, v_nmntvnodes);
}
TAILQ_REMOVE(&vp->v_mount->mnt_nvnodelist, vp, v_nmntvnodes);
}
int
getnewvnode(enum vtagtype tag, struct mount *mp,
struct vnode **vpp, int lktimeout, int lkflags)
{
struct vnode *vp;
KKASSERT(mp != NULL);
vp = allocvnode(lktimeout, lkflags);
vp->v_tag = tag;
vp->v_data = NULL;
vp->v_ops = &mp->mnt_vn_use_ops;
vp->v_pbuf_count = nswbuf_kva / NSWBUF_SPLIT;
insmntque(vp, mp);
*vpp = vp;
return (0);
}
int
getspecialvnode(enum vtagtype tag, struct mount *mp,
struct vop_ops **ops,
struct vnode **vpp, int lktimeout, int lkflags)
{
struct vnode *vp;
vp = allocvnode(lktimeout, lkflags);
vp->v_tag = tag;
vp->v_data = NULL;
vp->v_ops = ops;
if (mp == NULL)
mp = &dummymount;
insmntque(vp, mp);
*vpp = vp;
return (0);
}
int
vfs_busy(struct mount *mp, int flags)
{
int lkflags;
atomic_add_int(&mp->mnt_refs, 1);
lwkt_gettoken(&mp->mnt_token);
if (mp->mnt_kern_flag & MNTK_UNMOUNT) {
if (flags & LK_NOWAIT) {
lwkt_reltoken(&mp->mnt_token);
atomic_add_int(&mp->mnt_refs, -1);
return (ENOENT);
}
mp->mnt_kern_flag |= MNTK_MWAIT;
tsleep((caddr_t)mp, 0, "vfs_busy", 0);
lwkt_reltoken(&mp->mnt_token);
atomic_add_int(&mp->mnt_refs, -1);
return (ENOENT);
}
lkflags = LK_SHARED;
if (lockmgr(&mp->mnt_lock, lkflags))
panic("vfs_busy: unexpected lock failure");
lwkt_reltoken(&mp->mnt_token);
return (0);
}
void
vfs_unbusy(struct mount *mp)
{
mount_hold(mp);
atomic_add_int(&mp->mnt_refs, -1);
lockmgr(&mp->mnt_lock, LK_RELEASE);
mount_drop(mp);
}
int
vfs_rootmountalloc(char *fstypename, char *devname, struct mount **mpp)
{
struct vfsconf *vfsp;
struct mount *mp;
if (fstypename == NULL)
return (ENODEV);
vfsp = vfsconf_find_by_name(fstypename);
if (vfsp == NULL)
return (ENODEV);
mp = kmalloc(sizeof(struct mount), M_MOUNT, M_WAITOK | M_ZERO);
mount_init(mp, vfsp->vfc_vfsops);
lockinit(&mp->mnt_lock, "vfslock", VLKTIMEOUT, 0);
lockinit(&mp->mnt_renlock, "renamlk", VLKTIMEOUT, 0);
vfs_busy(mp, 0);
mp->mnt_vfc = vfsp;
mp->mnt_pbuf_count = nswbuf_kva / NSWBUF_SPLIT;
vfsp->vfc_refcount++;
mp->mnt_stat.f_type = vfsp->vfc_typenum;
mp->mnt_flag |= MNT_RDONLY;
mp->mnt_flag |= vfsp->vfc_flags & MNT_VISFLAGMASK;
strncpy(mp->mnt_stat.f_fstypename, vfsp->vfc_name, MFSNAMELEN);
copystr(devname, mp->mnt_stat.f_mntfromname, MNAMELEN - 1, 0);
if (vfsp->vfc_flags & VFCF_MPSAFE)
mp->mnt_kern_flag |= MNTK_ALL_MPSAFE;
*mpp = mp;
return (0);
}
void
mount_init(struct mount *mp, struct vfsops *ops)
{
lockinit(&mp->mnt_lock, "vfslock", hz*5, 0);
lockinit(&mp->mnt_renlock, "renamlk", hz*5, 0);
lwkt_token_init(&mp->mnt_token, "permnt");
TAILQ_INIT(&mp->mnt_vnodescan_list);
TAILQ_INIT(&mp->mnt_nvnodelist);
TAILQ_INIT(&mp->mnt_reservedvnlist);
TAILQ_INIT(&mp->mnt_jlist);
mp->mnt_nvnodelistsize = 0;
mp->mnt_flag = 0;
mp->mnt_hold = 1;
mp->mnt_iosize_max = MAXPHYS;
mp->mnt_op = ops;
if (ops == NULL || (ops->vfs_flags & VFSOPSF_NOSYNCERTHR) == 0)
vn_syncer_thr_create(mp);
}
void
mount_hold(struct mount *mp)
{
atomic_add_int(&mp->mnt_hold, 1);
}
void
mount_drop(struct mount *mp)
{
if (atomic_fetchadd_int(&mp->mnt_hold, -1) == 1) {
KKASSERT(mp->mnt_refs == 0);
kfree(mp, M_MOUNT);
}
}
struct mount *
vfs_getvfs(fsid_t *fsid)
{
struct mount *mp;
lwkt_gettoken_shared(&mountlist_token);
mp = mount_rb_tree_RB_LOOKUP_FSID(&mounttree, fsid);
if (mp)
mount_hold(mp);
lwkt_reltoken(&mountlist_token);
return (mp);
}
void
vfs_getnewfsid(struct mount *mp)
{
fsid_t tfsid;
int mtype;
int error;
char *retbuf;
char *freebuf;
mtype = mp->mnt_vfc->vfc_typenum;
tfsid.val[1] = mtype;
error = cache_fullpath(NULL, &mp->mnt_ncmounton, NULL,
&retbuf, &freebuf, 0);
if (error) {
tfsid.val[0] = makeudev(255, 0);
} else {
tfsid.val[0] = makeudev(255,
iscsi_crc32(retbuf, strlen(retbuf)) &
~makeudev(255, 0));
kfree(freebuf, M_TEMP);
}
mp->mnt_stat.f_fsid.val[0] = tfsid.val[0];
mp->mnt_stat.f_fsid.val[1] = tfsid.val[1];
}
void
vfs_setfsid(struct mount *mp, fsid_t *template)
{
bzero(&mp->mnt_stat.f_fsid, sizeof(mp->mnt_stat.f_fsid));
#if 0
struct mount *mptmp;
lwkt_gettoken(&mntid_token);
for (;;) {
mptmp = vfs_getvfs(template);
if (mptmp == NULL)
break;
mount_drop(mptmp);
++template->val[1];
}
lwkt_reltoken(&mntid_token);
#endif
mp->mnt_stat.f_fsid = *template;
}
static struct thread *vnlruthread;
static void
vnlru_proc(void)
{
struct thread *td = curthread;
EVENTHANDLER_REGISTER(shutdown_pre_sync, shutdown_kproc, td,
SHUTDOWN_PRI_FIRST);
for (;;) {
int ncachedandinactive;
kproc_suspend_loop();
synchronizevnodecount();
ncachedandinactive = countcachedandinactivevnodes();
if (numvnodes >= maxvnodes * 9 / 10 &&
ncachedandinactive >= maxvnodes * 5 / 10) {
int count = numvnodes - maxvnodes * 9 / 10;
if (count > (ncachedandinactive) / 100)
count = (ncachedandinactive) / 100;
if (count < 5)
count = 5;
freesomevnodes(count);
}
cache_hysteresis(0);
synchronizevnodecount();
ncachedandinactive = countcachedandinactivevnodes();
if (numvnodes <= maxvnodes * 9 / 10 ||
ncachedandinactive <= maxvnodes * 5 / 10) {
tsleep(vnlruthread, 0, "vlruwt", hz);
continue;
}
tsleep(vnlruthread, 0, "vlruwt", 1);
}
}
void
mountlist_insert(struct mount *mp, int how)
{
int lim = 0x01000000;
lwkt_gettoken(&mountlist_token);
if (how == MNTINS_FIRST)
TAILQ_INSERT_HEAD(&mountlist, mp, mnt_list);
else
TAILQ_INSERT_TAIL(&mountlist, mp, mnt_list);
while (mount_rb_tree_RB_INSERT(&mounttree, mp)) {
int32_t val;
val = mp->mnt_stat.f_fsid.val[0];
val = ((val & 0xFFFF0000) >> 8) | (val & 0x000000FF);
++val;
val = ((val << 8) & 0xFFFF0000) | (val & 0x000000FF);
mp->mnt_stat.f_fsid.val[0] = val;
if (--lim == 0) {
lim = 0x01000000;
mp->mnt_stat.f_fsid.val[1] += 0x0100;
kprintf("mountlist_insert: fsid collision, "
"too many mounts\n");
}
}
lwkt_reltoken(&mountlist_token);
}
int
mountlist_interlock(int (*callback)(struct mount *), struct mount *mp)
{
int error;
lwkt_gettoken(&mountlist_token);
error = callback(mp);
lwkt_reltoken(&mountlist_token);
return (error);
}
struct mount *
mountlist_boot_getfirst(void)
{
return(TAILQ_FIRST(&mountlist));
}
void
mountlist_remove(struct mount *mp)
{
struct mountscan_info *msi;
lwkt_gettoken(&mountlist_token);
TAILQ_FOREACH(msi, &mountscan_list, msi_entry) {
if (msi->msi_node == mp) {
if (msi->msi_how & MNTSCAN_FORWARD)
msi->msi_node = TAILQ_NEXT(mp, mnt_list);
else
msi->msi_node = TAILQ_PREV(mp, mntlist,
mnt_list);
}
}
TAILQ_REMOVE(&mountlist, mp, mnt_list);
mount_rb_tree_RB_REMOVE(&mounttree, mp);
lwkt_reltoken(&mountlist_token);
}
int
mountlist_exists(struct mount *mp)
{
int node_exists = 0;
struct mount* lmp;
lwkt_gettoken_shared(&mountlist_token);
TAILQ_FOREACH(lmp, &mountlist, mnt_list) {
if (lmp == mp) {
node_exists = 1;
break;
}
}
lwkt_reltoken(&mountlist_token);
return(node_exists);
}
int
mountlist_scan(int (*callback)(struct mount *, void *), void *data, int how)
{
struct mountscan_info info;
struct mount *mp;
int count;
int res;
int dounlock = ((how & MNTSCAN_NOUNLOCK) == 0);
lwkt_gettoken(&mountlist_token);
info.msi_how = how;
info.msi_node = NULL;
TAILQ_INSERT_TAIL(&mountscan_list, &info, msi_entry);
lwkt_reltoken(&mountlist_token);
res = 0;
lwkt_gettoken_shared(&mountlist_token);
if (how & MNTSCAN_FORWARD) {
info.msi_node = TAILQ_FIRST(&mountlist);
while ((mp = info.msi_node) != NULL) {
mount_hold(mp);
if (how & MNTSCAN_NOBUSY) {
if (dounlock)
lwkt_reltoken(&mountlist_token);
count = callback(mp, data);
if (dounlock)
lwkt_gettoken_shared(&mountlist_token);
} else if (vfs_busy(mp, LK_NOWAIT) == 0) {
if (dounlock)
lwkt_reltoken(&mountlist_token);
count = callback(mp, data);
if (dounlock)
lwkt_gettoken_shared(&mountlist_token);
if (mp == info.msi_node)
vfs_unbusy(mp);
} else {
count = 0;
}
mount_drop(mp);
if (count < 0)
break;
res += count;
if (mp == info.msi_node)
info.msi_node = TAILQ_NEXT(mp, mnt_list);
}
} else if (how & MNTSCAN_REVERSE) {
info.msi_node = TAILQ_LAST(&mountlist, mntlist);
while ((mp = info.msi_node) != NULL) {
mount_hold(mp);
if (how & MNTSCAN_NOBUSY) {
if (dounlock)
lwkt_reltoken(&mountlist_token);
count = callback(mp, data);
if (dounlock)
lwkt_gettoken_shared(&mountlist_token);
} else if (vfs_busy(mp, LK_NOWAIT) == 0) {
if (dounlock)
lwkt_reltoken(&mountlist_token);
count = callback(mp, data);
if (dounlock)
lwkt_gettoken_shared(&mountlist_token);
if (mp == info.msi_node)
vfs_unbusy(mp);
} else {
count = 0;
}
mount_drop(mp);
if (count < 0)
break;
res += count;
if (mp == info.msi_node)
info.msi_node = TAILQ_PREV(mp, mntlist,
mnt_list);
}
}
lwkt_reltoken(&mountlist_token);
lwkt_gettoken(&mountlist_token);
TAILQ_REMOVE(&mountscan_list, &info, msi_entry);
lwkt_reltoken(&mountlist_token);
return(res);
}
static struct kproc_desc vnlru_kp = {
"vnlru",
vnlru_proc,
&vnlruthread
};
SYSINIT(vnlru, SI_SUB_KTHREAD_UPDATE, SI_ORDER_FIRST, kproc_start, &vnlru_kp);
void
insmntque(struct vnode *vp, struct mount *mp)
{
struct mount *omp;
if ((omp = vp->v_mount) != NULL) {
lwkt_gettoken(&omp->mnt_token);
KKASSERT(omp == vp->v_mount);
KASSERT(omp->mnt_nvnodelistsize > 0,
("bad mount point vnode list size"));
vremovevnodemnt(vp);
omp->mnt_nvnodelistsize--;
lwkt_reltoken(&omp->mnt_token);
}
if (mp == NULL) {
vp->v_mount = NULL;
return;
}
lwkt_gettoken(&mp->mnt_token);
vp->v_mount = mp;
if (mp->mnt_syncer) {
TAILQ_INSERT_BEFORE(mp->mnt_syncer, vp, v_nmntvnodes);
} else {
TAILQ_INSERT_TAIL(&mp->mnt_nvnodelist, vp, v_nmntvnodes);
}
mp->mnt_nvnodelistsize++;
lwkt_reltoken(&mp->mnt_token);
}
int
vmntvnodescan(
struct mount *mp,
int flags,
int (*fastfunc)(struct mount *mp, struct vnode *vp, void *data),
int (*slowfunc)(struct mount *mp, struct vnode *vp, void *data),
void *data
) {
struct vmntvnodescan_info info;
struct vnode *vp;
int r = 0;
int maxcount = mp->mnt_nvnodelistsize * 2;
int stopcount = 0;
int count = 0;
lwkt_gettoken(&mp->mnt_token);
if (flags & VMSC_ONEPASS)
stopcount = mp->mnt_nvnodelistsize;
info.vp = TAILQ_FIRST(&mp->mnt_nvnodelist);
TAILQ_INSERT_TAIL(&mp->mnt_vnodescan_list, &info, entry);
while ((vp = info.vp) != NULL) {
if (--maxcount == 0) {
kprintf("Warning: excessive fssync iteration\n");
maxcount = mp->mnt_nvnodelistsize * 2;
}
if (vp->v_type == VNON)
goto next;
KKASSERT(vp->v_mount == mp);
if (fastfunc) {
if ((r = fastfunc(mp, vp, data)) < 0) {
r = 0;
goto next;
}
if (r)
break;
}
if (slowfunc) {
int error;
switch(flags & (VMSC_GETVP|VMSC_GETVX|VMSC_NOWAIT)) {
case VMSC_GETVP:
error = vget(vp, LK_EXCLUSIVE);
break;
case VMSC_GETVP|VMSC_NOWAIT:
error = vget(vp, LK_EXCLUSIVE|LK_NOWAIT);
break;
case VMSC_GETVX:
vx_get(vp);
error = 0;
break;
default:
error = 0;
break;
}
if (error)
goto next;
if (info.vp == vp && vp->v_type != VNON)
r = slowfunc(mp, vp, data);
switch(flags & (VMSC_GETVP|VMSC_GETVX|VMSC_NOWAIT)) {
case VMSC_GETVP:
case VMSC_GETVP|VMSC_NOWAIT:
vput(vp);
break;
case VMSC_GETVX:
vx_put(vp);
break;
default:
break;
}
if (r != 0)
break;
}
next:
if (++count == 10000) {
tsleep(mp, 0, "vnodescn", 1);
count = 0;
}
if (--stopcount == 0)
break;
if (info.vp == vp)
info.vp = TAILQ_NEXT(vp, v_nmntvnodes);
}
TAILQ_REMOVE(&mp->mnt_vnodescan_list, &info, entry);
lwkt_reltoken(&mp->mnt_token);
return(r);
}
static int debug_busyprt = 0;
SYSCTL_INT(_vfs, OID_AUTO, debug_busyprt, CTLFLAG_RW, &debug_busyprt, 0, "");
static int vflush_scan(struct mount *mp, struct vnode *vp, void *data);
struct vflush_info {
int flags;
int busy;
thread_t td;
};
int
vflush(struct mount *mp, int rootrefs, int flags)
{
struct thread *td = curthread;
struct vnode *rootvp = NULL;
int error;
struct vflush_info vflush_info;
if (rootrefs > 0) {
KASSERT((flags & (SKIPSYSTEM | WRITECLOSE)) == 0,
("vflush: bad args"));
if ((error = VFS_ROOT(mp, &rootvp)) != 0) {
if ((flags & FORCECLOSE) == 0)
return (error);
rootrefs = 0;
}
if (rootrefs)
vput(rootvp);
}
vflush_info.busy = 0;
vflush_info.flags = flags;
vflush_info.td = td;
vmntvnodescan(mp, VMSC_GETVX, NULL, vflush_scan, &vflush_info);
if (rootrefs > 0 && (flags & FORCECLOSE) == 0) {
KASSERT(vflush_info.busy > 0, ("vflush: not busy"));
KASSERT(VREFCNT(rootvp) >= rootrefs, ("vflush: rootrefs"));
if (vflush_info.busy == 1 && VREFCNT(rootvp) == rootrefs) {
vx_lock(rootvp);
vgone_vxlocked(rootvp);
vx_unlock(rootvp);
vflush_info.busy = 0;
}
}
if (vflush_info.busy)
return (EBUSY);
for (; rootrefs > 0; rootrefs--)
vrele(rootvp);
return (0);
}
static int
vflush_scan(struct mount *mp, struct vnode *vp, void *data)
{
struct vflush_info *info = data;
struct vattr vattr;
int flags = info->flags;
atomic_set_int(&vp->v_refcnt, VREF_FINALIZE);
if ((flags & SKIPSYSTEM) && (vp->v_flag & VSYSTEM)) {
return(0);
}
if (vp->v_type == VCHR || vp->v_type == VBLK)
flags &= ~(WRITECLOSE|FORCECLOSE);
if ((flags & WRITECLOSE) &&
(vp->v_type == VNON ||
(VOP_GETATTR(vp, &vattr) == 0 &&
vattr.va_nlink > 0)) &&
(vp->v_writecount == 0 || vp->v_type != VREG)) {
return(0);
}
if (VREFCNT(vp) <= 1) {
vgone_vxlocked(vp);
return(0);
}
if (flags & FORCECLOSE) {
vhold(vp);
vgone_vxlocked(vp);
if (vp->v_mount == NULL)
insmntque(vp, &dummymount);
vdrop(vp);
return(0);
}
if (vp->v_type == VCHR || vp->v_type == VBLK)
kprintf("vflush: Warning, cannot destroy busy device vnode\n");
if (debug_busyprt) {
const char *filename;
spin_lock(&vp->v_spin);
filename = TAILQ_FIRST(&vp->v_namecache) ?
TAILQ_FIRST(&vp->v_namecache)->nc_name : "?";
spin_unlock(&vp->v_spin);
kprintf("vflush: busy vnode (%p) %s\n", vp, filename);
}
++info->busy;
return(0);
}
void
add_bio_ops(struct bio_ops *ops)
{
TAILQ_INSERT_TAIL(&bio_ops_list, ops, entry);
}
void
rem_bio_ops(struct bio_ops *ops)
{
TAILQ_REMOVE(&bio_ops_list, ops, entry);
}
void
bio_ops_sync(struct mount *mp)
{
struct bio_ops *ops;
if (mp) {
if ((ops = mp->mnt_bioops) != NULL)
ops->io_sync(mp);
} else {
TAILQ_FOREACH(ops, &bio_ops_list, entry) {
ops->io_sync(NULL);
}
}
}
struct mount *
mount_get_by_nc(struct namecache *ncp)
{
struct mount *mp = NULL;
lwkt_gettoken_shared(&mountlist_token);
TAILQ_FOREACH(mp, &mountlist, mnt_list) {
if (ncp == mp->mnt_ncmountpt.ncp)
break;
}
lwkt_reltoken(&mountlist_token);
return (mp);
}