cachefs_vfsops.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/cred.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/pathname.h>
#include <sys/uio.h>
#include <sys/tiuser.h>
#include <sys/sysmacros.h>
#include <sys/kmem.h>
#include <sys/mount.h>
#include <sys/ioctl.h>
#include <sys/statvfs.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/utsname.h>
#include <sys/bootconf.h>
#include <sys/reboot.h>
#include <sys/modctl.h>
#include <rpc/types.h>
#include <sys/fs/cachefs_fs.h>
#include <sys/fs/cachefs_log.h>
#include <sys/mkdev.h>
#include <sys/dnlc.h>
#include <sys/policy.h>
#include "fs/fs_subr.h"
extern kmutex_t cachefs_kmem_lock;
kmutex_t cachefs_kstat_key_lock;
/* forward declarations */
static int cachefs_remount(struct vfs *, struct mounta *);
static void cachefs_delete_cachep(cachefscache_t *);
#define CFS_MAPSIZE 256
kmutex_t cachefs_cachelock; /* Cache list mutex */
cachefscache_t *cachefs_cachelist = NULL; /* Cache struct list */
int cachefs_mount_retries = 3;
kmutex_t cachefs_minor_lock; /* Lock for minor device map */
major_t cachefs_major = 0;
minor_t cachefs_minor = 0;
cachefs_kstat_key_t *cachefs_kstat_key = NULL;
int cachefs_kstat_key_n = 0;
static uint32_t cachefs_nfsv4_warnmsg = FALSE;
/*
* cachefs vfs operations.
*/
static int cachefs_mount(vfs_t *, vnode_t *, struct mounta *, cred_t *);
static int cachefs_unmount(vfs_t *, int, cred_t *);
static int cachefs_root(vfs_t *, vnode_t **);
static int cachefs_statvfs(register vfs_t *, struct statvfs64 *);
static int cachefs_sync(vfs_t *, short, cred_t *);
/*
* Initialize the vfs structure
*/
int cachefsfstyp;
int cnodesize = 0;
int
cachefs_init_vfsops(int fstype)
{
static const fs_operation_def_t cachefs_vfsops_template[] = {
VFSNAME_MOUNT, cachefs_mount,
VFSNAME_UNMOUNT, cachefs_unmount,
VFSNAME_ROOT, cachefs_root,
VFSNAME_STATVFS, cachefs_statvfs,
VFSNAME_SYNC, (fs_generic_func_p) cachefs_sync,
NULL, NULL
};
int error;
error = vfs_setfsops(fstype, cachefs_vfsops_template, NULL);
if (error != 0)
return (error);
cachefsfstyp = fstype;
return (0);
}
dev_t
cachefs_mkmntdev(void)
{
dev_t cachefs_dev;
mutex_enter(&cachefs_minor_lock);
do {
cachefs_minor = (cachefs_minor + 1) & MAXMIN32;
cachefs_dev = makedevice(cachefs_major, cachefs_minor);
} while (vfs_devismounted(cachefs_dev));
mutex_exit(&cachefs_minor_lock);
return (cachefs_dev);
}
/*
* vfs operations
*/
static int
cachefs_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr)
{
char *data = uap->dataptr;
STRUCT_DECL(cachefs_mountargs, map);
struct cachefsoptions *cfs_options;
char *backfs, *cacheid, *cachedir;
vnode_t *cachedirvp = NULL;
vnode_t *backrootvp = NULL;
cachefscache_t *cachep = NULL;
fscache_t *fscp = NULL;
cnode_t *cp;
struct fid *cookiep = NULL;
struct vattr *attrp = NULL;
dev_t cachefs_dev; /* devid for this mount */
int error = 0;
int retries = cachefs_mount_retries;
ino64_t fsid;
cfs_cid_t cid;
char *backmntpt;
ino64_t backfileno;
struct vfs *backvfsp;
size_t strl;
char tmpstr[MAXPATHLEN];
vnode_t *tmpdirvp = NULL;
ulong_t maxfilesizebits;
uint32_t valid_fid;
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_VFSOP)
printf("cachefs_mount: ENTER cachefs_mntargs %p\n", data);
#endif
/*
* Make sure we have sufficient privileges.
*/
if ((error = secpolicy_fs_mount(cr, mvp, vfsp)) != 0)
goto out;
/*
* make sure we're mounting on a directory
*/
if (mvp->v_type != VDIR) {
error = ENOTDIR;
goto out;
}
/*
* Determine the zone we're being mounted into, and make sure it's the
* global zone.
*/
if (getzoneid() == GLOBAL_ZONEID) {
zone_t *mntzone;
mntzone = zone_find_by_path(refstr_value(vfsp->vfs_mntpt));
ASSERT(mntzone != NULL);
zone_rele(mntzone);
if (mntzone != curproc->p_zone) {
error = EBUSY;
goto out;
}
} else {
error = EPERM;
goto out;
}
if (uap->flags & MS_REMOUNT) {
error = cachefs_remount(vfsp, uap);
goto out;
}
/*
* Assign a unique device id to the mount
*/
cachefs_dev = cachefs_mkmntdev();
#ifdef _LP64
/*
* It's not a good idea to make fsid bigger since that'll
* have adverse effects on nfs filehandles. For now assume that
* cachefs be used on devices that fit into dev32_t's.
*/
if (cachefs_dev == NODEV) {
error = EOVERFLOW;
goto out;
}
#endif
/*
* Copy in the arguments
*/
STRUCT_INIT(map, get_udatamodel());
error = copyin(data, STRUCT_BUF(map),
SIZEOF_STRUCT(cachefs_mountargs, DATAMODEL_NATIVE));
if (error) {
goto out;
}
cfs_options = (struct cachefsoptions *)STRUCT_FADDR(map, cfs_options);
cacheid = (char *)STRUCT_FGETP(map, cfs_cacheid);
if ((cfs_options->opt_flags &
(CFS_WRITE_AROUND|CFS_NONSHARED|CFS_BACKFS_NFSV4)) == 0) {
error = EINVAL;
goto out;
}
if ((cfs_options->opt_popsize % MAXBSIZE) != 0) {
error = EINVAL;
goto out;
}
/*
* Get the cache directory vp
*/
/*LINTED 32-bit pointer casting okay*/
cachedir = (char *)STRUCT_FGETP(map, cfs_cachedir);
error = lookupname(cachedir, UIO_USERSPACE, FOLLOW,
NULLVPP, &cachedirvp);
if (error)
goto out;
/*
* Make sure the thing we just looked up is a directory
*/
if (cachedirvp->v_type != VDIR) {
cmn_err(CE_WARN, "cachefs_mount: cachedir not a directory\n");
error = EINVAL;
goto out;
}
/*
* Make sure the cache doesn't live in cachefs!
*/
if (vn_matchops(cachedirvp, cachefs_getvnodeops())) {
cmn_err(CE_WARN, "cachefs_mount: cachedir in cachefs!\n");
error = EINVAL;
goto out;
}
/* if the backfs is mounted */
/*LINTED 32-bit pointer casting okay*/
if ((backfs = STRUCT_FGETP(map, cfs_backfs)) != NULL) {
/*
* Get the back file system root vp
*/
error = lookupname(backfs, UIO_USERSPACE, FOLLOW,
NULLVPP, &backrootvp);
if (error)
goto out;
/*
* Make sure the thing we just looked up is a directory
* and a root of a file system
*/
if (backrootvp->v_type != VDIR ||
!(backrootvp->v_flag & VROOT)) {
cmn_err(CE_WARN,
"cachefs_mount: backpath not a directory\n");
error = EINVAL;
goto out;
}
/*
* Get the fid and attributes for the root of the
* backfilesystem, except if NFSv4 is in use,
* in which case we get the attributes only (the
* (VOP_FID() operation called by cachefs_get_cookie()
* is not supported in NFSv4).
*/
cookiep = cachefs_kmem_alloc(sizeof (struct fid), KM_SLEEP);
attrp = cachefs_kmem_alloc(sizeof (struct vattr), KM_SLEEP);
if ((cfs_options->opt_flags & CFS_BACKFS_NFSV4)) {
valid_fid = FALSE;
} else {
valid_fid = TRUE;
}
error = cachefs_getcookie(backrootvp, cookiep, attrp, cr,
valid_fid);
if (error)
goto out;
backmntpt = backfs;
backfileno = attrp->va_nodeid;
backvfsp = backrootvp->v_vfsp;
} else {
backmntpt = NULL;
backfileno = 0;
backvfsp = NULL;
}
again:
/*
* In SVR4 it's not acceptable to stack up mounts
* unless MS_OVERLAY specified.
*/
mutex_enter(&mvp->v_lock);
if (((uap->flags & MS_OVERLAY) == 0) &&
((mvp->v_count != 1) || (mvp->v_flag & VROOT))) {
mutex_exit(&mvp->v_lock);
error = EBUSY;
goto out;
}
mutex_exit(&mvp->v_lock);
/*
* Lock out other mounts and unmounts until we safely have
* a mounted fscache object.
*/
mutex_enter(&cachefs_cachelock);
/*
* Find the cache structure
*/
for (cachep = cachefs_cachelist; cachep != NULL;
cachep = cachep->c_next) {
if (cachep->c_dirvp == cachedirvp)
break;
}
/* if the cache object does not exist, then create it */
if (cachep == NULL) {
cachep = cachefs_cache_create();
error = cachefs_cache_activate_ro(cachep, cachedirvp);
if (error) {
cachefs_cache_destroy(cachep);
cachep = NULL;
goto out;
}
if ((cfs_options->opt_flags & CFS_NOFILL) == 0)
cachefs_cache_activate_rw(cachep);
else
cfs_options->opt_flags &= ~CFS_NOFILL;
cachep->c_next = cachefs_cachelist;
cachefs_cachelist = cachep;
} else if (cfs_options->opt_flags & CFS_NOFILL) {
cmn_err(CE_WARN,
"CacheFS: attempt to convert nonempty cache "
"to NOFILL mode");
error = EINVAL;
goto out;
}
/* get the fscache id for this name */
error = fscache_name_to_fsid(cachep, cacheid, &fsid);
if (error) {
fsid = 0;
}
/* find the fscache object for this mount point or create it */
mutex_enter(&cachep->c_fslistlock);
fscp = fscache_list_find(cachep, fsid);
if (fscp == NULL) {
fscp = fscache_create(cachep);
error = fscache_activate(fscp, fsid, cacheid,
cfs_options, backfileno);
if (error) {
fscache_destroy(fscp);
fscp = NULL;
mutex_exit(&cachep->c_fslistlock);
if ((error == ENOSPC) && (retries-- > 0)) {
mutex_exit(&cachefs_cachelock);
delay(6 * hz);
goto again;
}
goto out;
}
fscache_list_add(cachep, fscp);
} else {
/* compare the options to make sure they are compatable */
error = fscache_compare_options(fscp, cfs_options);
if (error) {
cmn_err(CE_WARN,
"CacheFS: mount failed, options do not match.");
fscp = NULL;
mutex_exit(&cachep->c_fslistlock);
goto out;
}
/* copy options into the fscache */
mutex_enter(&fscp->fs_fslock);
fscp->fs_info.fi_mntflags = cfs_options->opt_flags;
fscp->fs_info.fi_popsize = cfs_options->opt_popsize;
fscp->fs_info.fi_fgsize = cfs_options->opt_fgsize;
fscp->fs_flags |= CFS_FS_DIRTYINFO;
mutex_exit(&fscp->fs_fslock);
}
fscache_hold(fscp);
error = 0;
if (fscp->fs_fscdirvp) {
error = VOP_LOOKUP(fscp->fs_fscdirvp, CACHEFS_DLOG_FILE,
&tmpdirvp, NULL, 0, NULL, kcred);
/*
* If a log file exists and the cache is being mounted without
* the snr (aka disconnectable) option, return an error.
*/
if ((error == 0) &&
!(cfs_options->opt_flags & CFS_DISCONNECTABLE)) {
mutex_exit(&cachep->c_fslistlock);
cmn_err(CE_WARN, "cachefs: log exists and "
"disconnectable option not specified\n");
error = EINVAL;
goto out;
}
}
/*
* Acquire the name of the mount point
*/
if (fscp->fs_mntpt == NULL) {
/*
* the string length returned by copystr includes the
* terminating NULL character, unless a NULL string is
* passed in, then the string length is unchanged.
*/
strl = 0;
tmpstr[0] = '\0';
(void) copyinstr(uap->dir, tmpstr, MAXPATHLEN, &strl);
if (strl > 1) {
fscp->fs_mntpt = kmem_alloc(strl, KM_SLEEP);
(void) strncpy(fscp->fs_mntpt, tmpstr, strl);
}
/*
* else fscp->fs_mntpt is unchanged(still NULL) try again
* next time
*/
}
/*
* Acquire the name of the server
*/
if (fscp->fs_hostname == NULL) {
strl = 0;
tmpstr[0] = '\0';
/*LINTED 32-bit pointer casting okay*/
(void) copyinstr((char *)STRUCT_FGETP(map, cfs_hostname),
tmpstr, MAXPATHLEN, &strl);
if (strl > 1) {
fscp->fs_hostname = kmem_alloc(strl, KM_SLEEP);
(void) strncpy(fscp->fs_hostname, tmpstr, strl);
}
/*
* else fscp->fs_hostname remains unchanged (is still NULL)
*/
}
/*
* Acquire name of the back filesystem
*/
if (fscp->fs_backfsname == NULL) {
strl = 0;
tmpstr[0] = '\0';
/*LINTED 32-bit pointer casting okay*/
(void) copyinstr((char *)STRUCT_FGETP(map, cfs_backfsname),
tmpstr, MAXPATHLEN, &strl);
if (strl > 1) {
fscp->fs_backfsname = kmem_alloc(strl, KM_SLEEP);
(void) strncpy(fscp->fs_backfsname, tmpstr, strl);
}
/*
* else fscp->fs_backfsname remains unchanged (is still NULL)
*/
}
backfileno = fscp->fs_info.fi_root;
mutex_exit(&cachep->c_fslistlock);
/* see if fscache object is already mounted, it not, make it so */
error = fscache_mounted(fscp, vfsp, backvfsp);
if (error) {
/* fs cache was already mounted */
error = EBUSY;
goto out;
}
cachefs_kstat_mount(fscp, uap->dir, backmntpt, cachedir, cacheid);
/* set nfs style time out parameters */
fscache_acset(fscp, STRUCT_FGET(map, cfs_acregmin),
STRUCT_FGET(map, cfs_acregmax),
STRUCT_FGET(map, cfs_acdirmin), STRUCT_FGET(map, cfs_acdirmax));
vfsp->vfs_dev = cachefs_dev;
vfsp->vfs_data = (caddr_t)fscp;
vfs_make_fsid(&vfsp->vfs_fsid, cachefs_dev, cachefsfstyp);
vfsp->vfs_fstype = cachefsfstyp;
if (backvfsp)
vfsp->vfs_bsize = backvfsp->vfs_bsize;
else
vfsp->vfs_bsize = MAXBSIZE; /* XXX */
/* make a cnode for the root of the file system */
cid.cid_flags = 0;
cid.cid_fileno = backfileno;
error = cachefs_cnode_make(&cid, fscp, (valid_fid ? cookiep : NULL),
attrp, backrootvp, cr, CN_ROOT, &cp);
if (error) {
cmn_err(CE_WARN, "cachefs_mount: can't create root cnode\n");
goto out;
}
/* stick the root cnode in the fscache object */
mutex_enter(&fscp->fs_fslock);
fscp->fs_rootvp = CTOV(cp);
fscp->fs_rootvp->v_flag |= VROOT;
fscp->fs_rootvp->v_type |= cp->c_attr.va_type;
ASSERT(fscp->fs_rootvp->v_type == VDIR);
/*
* Get the maxfilesize bits of the back file system.
*/
error = VOP_PATHCONF(backrootvp, _PC_FILESIZEBITS, &maxfilesizebits,
kcred);
if (error) {
cmn_err(CE_WARN,
"cachefs_mount: Can't get the FILESIZEBITS of the back root vnode \n");
goto out;
}
fscp->fs_offmax = (1LL << (maxfilesizebits - 1)) - 1;
mutex_exit(&fscp->fs_fslock);
/* remove the unmount file if it is there */
(void) VOP_REMOVE(fscp->fs_fscdirvp, CACHEFS_UNMNT_FILE, kcred);
/* wake up the cache worker if ANY packed pending work */
mutex_enter(&cachep->c_contentslock);
if (cachep->c_flags & CACHE_PACKED_PENDING)
cv_signal(&cachep->c_cwcv);
mutex_exit(&cachep->c_contentslock);
/*
* Warn that caching is disabled with NFSv4 first time around.
*/
if (!cachefs_nfsv4_warnmsg && CFS_ISFS_BACKFS_NFSV4(fscp)) {
cmn_err(CE_WARN,
"Cachefs has detected a mount with NFSv4: caching will"
" be disabled for this and other NFSv4 mounts\n");
cachefs_nfsv4_warnmsg = TRUE;
}
out:
/*
* make a log entry, if appropriate
*/
if ((cachep != NULL) &&
CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_MOUNT))
cachefs_log_mount(cachep, error, vfsp, fscp,
uap->dir, UIO_USERSPACE,
(STRUCT_BUF(map) != NULL) ? cacheid : NULL);
/*
* Cleanup our mess
*/
if (cookiep != NULL)
cachefs_kmem_free(cookiep, sizeof (struct fid));
if (cachedirvp != NULL)
VN_RELE(cachedirvp);
if (backrootvp != NULL)
VN_RELE(backrootvp);
if (fscp)
fscache_rele(fscp);
if (attrp)
cachefs_kmem_free(attrp, sizeof (struct vattr));
if (error) {
if (cachep) {
int xx;
/* lock the cachep's fslist */
mutex_enter(&cachep->c_fslistlock);
/*
* gc isn't necessary for list_mounted(), but
* we want to do it anyway.
*/
fscache_list_gc(cachep);
xx = fscache_list_mounted(cachep);
mutex_exit(&cachep->c_fslistlock);
/* if no more references to this cachep, punt it. */
if (xx == 0)
cachefs_delete_cachep(cachep);
mutex_exit(&cachefs_cachelock);
}
} else {
mutex_exit(&cachefs_cachelock);
}
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_VFSOP)
printf("cachefs_mount: EXIT\n");
#endif
return (error);
}
void
cachefs_kstat_mount(struct fscache *fscp,
char *umountpoint, char *ubackfs, char *ucachedir, char *cacheid)
{
cachefscache_t *cachep = fscp->fs_cache;
cachefs_kstat_key_t *key;
char *mountpoint = NULL, *backfs = NULL, *cachedir = NULL;
size_t len;
kstat_t *ksp;
int i, rc;
mountpoint = cachefs_kmem_alloc(MAXPATHLEN, KM_SLEEP);
if (copyinstr(umountpoint, mountpoint, MAXPATHLEN, &len) != 0)
goto out;
cachedir = cachefs_kmem_alloc(MAXPATHLEN, KM_SLEEP);
if (copyinstr(ucachedir, cachedir, MAXPATHLEN, &len) != 0)
goto out;
backfs = cachefs_kmem_alloc(MAXPATHLEN, KM_SLEEP);
if (backfs) {
if (copyinstr(ubackfs, backfs, MAXPATHLEN, &len) != 0)
goto out;
} else {
(void) strcpy(backfs, "no back file system");
}
ASSERT(strlen(mountpoint) < MAXPATHLEN);
ASSERT(strlen(backfs) < MAXPATHLEN);
ASSERT(strlen(cachedir) < MAXPATHLEN);
/* protect cachefs_kstat_key */
mutex_enter(&cachefs_kstat_key_lock);
/*
* XXXX If already there, why not go straight to it?
* We know that fscp->fs_kstat_id == i + 1
*/
i = fscp->fs_kstat_id - 1;
if ((i >= 0) && (i < cachefs_kstat_key_n))
rc = 1;
else
rc = i = 0;
for (; i < cachefs_kstat_key_n; i++) {
key = cachefs_kstat_key + i;
if (strcmp((char *)(uintptr_t)key->ks_mountpoint,
mountpoint) == 0 &&
strcmp((char *)(uintptr_t)key->ks_cachedir,
cachedir) == 0 &&
strcmp((char *)(uintptr_t)key->ks_cacheid, cacheid) == 0)
break;
if (rc) { /* direct key did not work - check all */
i = -1; /* will increment to zero in loop */
rc = 0;
}
}
if (i >= cachefs_kstat_key_n) {
key = cachefs_kmem_alloc((cachefs_kstat_key_n + 1) *
sizeof (cachefs_kstat_key_t), KM_SLEEP);
if (cachefs_kstat_key != NULL) {
bcopy(cachefs_kstat_key, key,
cachefs_kstat_key_n * sizeof (*key));
cachefs_kmem_free(cachefs_kstat_key,
cachefs_kstat_key_n * sizeof (*key));
}
cachefs_kstat_key = key;
key = cachefs_kstat_key + cachefs_kstat_key_n;
++cachefs_kstat_key_n;
rc = key->ks_id = cachefs_kstat_key_n; /* offset + 1 */
key->ks_mountpoint = (uint64_t)(uintptr_t)
cachefs_strdup(mountpoint);
key->ks_backfs = (uint64_t)(uintptr_t)cachefs_strdup(backfs);
key->ks_cachedir = (uint64_t)(uintptr_t)
cachefs_strdup(cachedir);
key->ks_cacheid = (uint64_t)(uintptr_t)cachefs_strdup(cacheid);
} else
rc = key->ks_id;
mutex_enter(&fscp->fs_fslock); /* protect fscp */
fscp->fs_kstat_id = rc;
mutex_exit(&fscp->fs_fslock); /* finished with fscp */
/* finished cachefs_kstat_key */
mutex_exit(&cachefs_kstat_key_lock);
key->ks_vfsp = (uint64_t)(uintptr_t)fscp->fs_cfsvfsp;
key->ks_mounted = 1;
/*
* we must not be holding any mutex that is a ks_lock field
* for one of the kstats when we invoke kstat_create,
* kstat_install, and friends.
*/
ASSERT(MUTEX_NOT_HELD(&cachefs_kstat_key_lock));
/* really should be EVERY cachep's c_log_mutex */
ASSERT(MUTEX_NOT_HELD(&cachep->c_log_mutex));
/* cachefs.#.log */
ksp = kstat_create("cachefs", fscp->fs_kstat_id, "log",
"misc", KSTAT_TYPE_RAW, 1,
KSTAT_FLAG_WRITABLE | KSTAT_FLAG_VIRTUAL);
if (ksp != NULL) {
ksp->ks_data = cachep->c_log_ctl;
ksp->ks_data_size = sizeof (cachefs_log_control_t);
ksp->ks_lock = &cachep->c_log_mutex;
ksp->ks_snapshot = cachefs_log_kstat_snapshot;
kstat_install(ksp);
}
/* cachefs.#.stats */
ksp = kstat_create("cachefs", fscp->fs_kstat_id, "stats",
"misc", KSTAT_TYPE_RAW, 1,
KSTAT_FLAG_WRITABLE | KSTAT_FLAG_VIRTUAL);
if (ksp != NULL) {
ksp->ks_data = fscp;
ksp->ks_data_size = sizeof (cachefs_stats_t);
ksp->ks_snapshot = cachefs_stats_kstat_snapshot;
kstat_install(ksp);
}
out:
if (mountpoint != NULL)
cachefs_kmem_free(mountpoint, MAXPATHLEN);
if (backfs != NULL)
cachefs_kmem_free(backfs, MAXPATHLEN);
if (cachedir != NULL)
cachefs_kmem_free(cachedir, MAXPATHLEN);
}
void
cachefs_kstat_umount(int ksid)
{
cachefs_kstat_key_t *k = cachefs_kstat_key + (ksid - 1);
ASSERT(k->ks_id == ksid);
k->ks_mounted = 0;
kstat_delete_byname("cachefs", ksid, "stats");
kstat_delete_byname("cachefs", ksid, "log");
}
int
cachefs_kstat_key_update(kstat_t *ksp, int rw)
{
cachefs_kstat_key_t *key = *((cachefs_kstat_key_t **)ksp->ks_data);
cachefs_kstat_key_t *k;
int i;
if (rw == KSTAT_WRITE)
return (EIO);
if (key == NULL)
return (EIO);
ksp->ks_data_size = cachefs_kstat_key_n * sizeof (*key);
for (i = 0; i < cachefs_kstat_key_n; i++) {
k = key + i;
ksp->ks_data_size +=
strlen((char *)(uintptr_t)k->ks_mountpoint) + 1;
ksp->ks_data_size +=
strlen((char *)(uintptr_t)k->ks_backfs) + 1;
ksp->ks_data_size +=
strlen((char *)(uintptr_t)k->ks_cachedir) + 1;
ksp->ks_data_size +=
strlen((char *)(uintptr_t)k->ks_cacheid) + 1;
}
ksp->ks_ndata = cachefs_kstat_key_n;
return (0);
}
int
cachefs_kstat_key_snapshot(kstat_t *ksp, void *buf, int rw)
{
cachefs_kstat_key_t *key = *((cachefs_kstat_key_t **)ksp->ks_data);
cachefs_kstat_key_t *k;
caddr_t s;
int i;
if (rw == KSTAT_WRITE)
return (EIO);
if (key == NULL)
return (0); /* paranoid */
bcopy(key, buf, cachefs_kstat_key_n * sizeof (*key));
key = buf;
s = (caddr_t)(key + cachefs_kstat_key_n);
for (i = 0; i < cachefs_kstat_key_n; i++) {
k = key + i;
(void) strcpy(s, (char *)(uintptr_t)k->ks_mountpoint);
k->ks_mountpoint = (uint64_t)(uintptr_t)(s - (uintptr_t)buf);
s += strlen(s) + 1;
(void) strcpy(s, (char *)(uintptr_t)k->ks_backfs);
k->ks_backfs = (uint64_t)(uintptr_t)(s - (uintptr_t)buf);
s += strlen(s) + 1;
(void) strcpy(s, (char *)(uintptr_t)k->ks_cachedir);
k->ks_cachedir = (uint64_t)(uintptr_t)(s - (uintptr_t)buf);
s += strlen(s) + 1;
(void) strcpy(s, (char *)(uintptr_t)k->ks_cacheid);
k->ks_cacheid = (uint64_t)(uintptr_t)(s - (uintptr_t)buf);
s += strlen(s) + 1;
}
return (0);
}
extern void cachefs_inactivate();
static int
cachefs_unmount(vfs_t *vfsp, int flag, cred_t *cr)
{
fscache_t *fscp = VFS_TO_FSCACHE(vfsp);
struct cachefscache *cachep = fscp->fs_cache;
int error;
int xx;
vnode_t *nmvp;
struct vattr attr;
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_VFSOP)
printf("cachefs_unmount: ENTER fscp %p\n", fscp);
#endif
if ((error = secpolicy_fs_unmount(cr, vfsp)) != 0)
goto out;
/*
* forced unmount is not supported by this file system
* and thus, ENOTSUP, is being returned.
*/
if (flag & MS_FORCE) {
error = ENOTSUP;
goto out;
}
/* if a log file exists don't allow the unmount */
if (fscp->fs_dlogfile) {
error = EBUSY;
goto out;
}
/*
* wait for the cache-wide async queue to drain. Someone
* here may be trying to sync our fscache...
*/
while (cachefs_async_halt(&fscp->fs_cache->c_workq, 0) == EBUSY) {
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_VFSOP)
printf("unmount: waiting for cache async queue...\n");
#endif
}
error = cachefs_async_halt(&fscp->fs_workq, 1);
if (error) {
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_VFSOP)
printf("cachefs_unmount: "
"cachefs_async_halt error %d\n", error);
#endif
goto out;
}
/*
* No active cnodes on this cache && rootvp refcnt == 1
*/
mutex_enter(&fscp->fs_fslock);
xx = fscp->fs_cnodecnt - fscp->fs_idlecnt;
ASSERT(xx >= 1);
if (xx > 1 || fscp->fs_rootvp->v_count != 1) {
mutex_exit(&fscp->fs_fslock);
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_VFSOP)
printf("cachefs_unmount: busy (cnodes active %d, idle "
"%d)\n", fscp->fs_cnodecnt, fscp->fs_idlecnt);
#endif
error = EBUSY;
goto out;
}
mutex_exit(&fscp->fs_fslock);
/* get rid of anything on the idle list */
ASSERT(fscp->fs_idleclean == 0);
cachefs_cnode_idleclean(fscp, 1);
if (fscp->fs_cnodecnt > 1) {
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_VFSOP)
printf("cachefs_unmount: busy (cnode count %d)\n",
fscp->fs_cnodecnt);
#endif
error = EBUSY;
goto out;
}
fscache_hold(fscp);
/* get rid of the root cnode */
if (cachefs_cnode_inactive(fscp->fs_rootvp, cr) == EBUSY) {
fscache_rele(fscp);
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_VFSOP)
printf("cachefs_unmount: busy (inactive failed)\n");
#endif
error = EBUSY;
goto out;
}
/* create the file indicating not mounted */
attr.va_mode = S_IFREG | 0666;
attr.va_uid = 0;
attr.va_gid = 0;
attr.va_type = VREG;
attr.va_mask = AT_TYPE | AT_MODE | AT_UID | AT_GID;
if (fscp->fs_fscdirvp != NULL)
xx = VOP_CREATE(fscp->fs_fscdirvp, CACHEFS_UNMNT_FILE, &attr,
NONEXCL, 0600, &nmvp, kcred, 0);
else
xx = ENOENT; /* for unmounting when NOCACHE */
if (xx == 0) {
VN_RELE(nmvp);
} else {
printf("could not create %s %d\n", CACHEFS_UNMNT_FILE, xx);
}
ASSERT(fscp->fs_cnodecnt == 0);
/* sync the file system just in case */
fscache_sync(fscp);
/* lock out other unmounts and mount */
mutex_enter(&cachefs_cachelock);
/* mark the file system as not mounted */
mutex_enter(&fscp->fs_fslock);
fscp->fs_flags &= ~CFS_FS_MOUNTED;
fscp->fs_rootvp = NULL;
if (fscp->fs_kstat_id > 0)
cachefs_kstat_umount(fscp->fs_kstat_id);
fscp->fs_kstat_id = 0;
/* drop the inum translation table */
if (fscp->fs_inum_size > 0) {
cachefs_kmem_free(fscp->fs_inum_trans,
fscp->fs_inum_size * sizeof (cachefs_inum_trans_t));
fscp->fs_inum_size = 0;
fscp->fs_inum_trans = NULL;
fscp->fs_flags &= ~CFS_FS_HASHPRINT;
}
mutex_exit(&fscp->fs_fslock);
fscache_rele(fscp);
/* get rid of any unused fscache objects */
mutex_enter(&cachep->c_fslistlock);
fscache_list_gc(cachep);
mutex_exit(&cachep->c_fslistlock);
/* get the number of mounts on this cache */
mutex_enter(&cachep->c_fslistlock);
xx = fscache_list_mounted(cachep);
mutex_exit(&cachep->c_fslistlock);
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_UMOUNT))
cachefs_log_umount(cachep, 0, vfsp);
/* if no mounts left, deactivate the cache */
if (xx == 0)
cachefs_delete_cachep(cachep);
mutex_exit(&cachefs_cachelock);
out:
if (error) {
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_UMOUNT))
cachefs_log_umount(cachep, error, vfsp);
}
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_VFSOP)
printf("cachefs_unmount: EXIT\n");
#endif
return (error);
}
/*
* remove the cache from the list of caches
*/
static void
cachefs_delete_cachep(cachefscache_t *cachep)
{
struct cachefscache **cachepp;
int found = 0;
ASSERT(MUTEX_HELD(&cachefs_cachelock));
for (cachepp = &cachefs_cachelist;
*cachepp != NULL;
cachepp = &(*cachepp)->c_next) {
if (*cachepp == cachep) {
*cachepp = cachep->c_next;
found++;
break;
}
}
ASSERT(found);
/* shut down the cache */
cachefs_cache_destroy(cachep);
}
static int
cachefs_root(vfs_t *vfsp, vnode_t **vpp)
{
/*LINTED alignment okay*/
struct fscache *fscp = (struct fscache *)vfsp->vfs_data;
ASSERT(fscp != NULL);
ASSERT(fscp->fs_rootvp != NULL);
if (getzoneid() != GLOBAL_ZONEID)
return (EPERM);
*vpp = fscp->fs_rootvp;
VN_HOLD(*vpp);
return (0);
}
/*
* Get file system statistics.
*/
static int
cachefs_statvfs(register vfs_t *vfsp, struct statvfs64 *sbp)
{
struct fscache *fscp = VFS_TO_FSCACHE(vfsp);
struct cache_label *lp = &fscp->fs_cache->c_label;
struct cache_usage *up = &fscp->fs_cache->c_usage;
int error;
if (getzoneid() != GLOBAL_ZONEID)
return (EPERM);
error = cachefs_cd_access(fscp, 0, 0);
if (error)
return (error);
if (fscp->fs_cdconnected == CFS_CD_CONNECTED) {
/*
* When connected return backfs stats
*/
error = VFS_STATVFS(fscp->fs_backvfsp, sbp);
} else {
/*
* Otherwise, just return the frontfs stats
*/
ASSERT(CFS_ISFS_BACKFS_NFSV4(fscp) == 0);
error = VFS_STATVFS(fscp->fs_fscdirvp->v_vfsp, sbp);
if (!error) {
dev32_t d32;
sbp->f_frsize = MAXBSIZE;
sbp->f_blocks = lp->cl_maxblks;
sbp->f_bfree = sbp->f_bavail =
lp->cl_maxblks - up->cu_blksused;
sbp->f_files = lp->cl_maxinodes;
sbp->f_ffree = sbp->f_favail =
lp->cl_maxinodes - up->cu_filesused;
(void) cmpldev(&d32, vfsp->vfs_dev);
sbp->f_fsid = d32;
}
}
cachefs_cd_release(fscp);
if (error)
return (error);
/*
* Make sure fstype is CFS.
*/
(void) strcpy(sbp->f_basetype, vfssw[vfsp->vfs_fstype].vsw_name);
bzero(sbp->f_fstr, sizeof (sbp->f_fstr));
return (0);
}
/*
* queue a request to sync the given fscache
*/
static void
queue_sync(struct cachefscache *cachep, cred_t *cr)
{
struct cachefs_req *rp;
rp = kmem_cache_alloc(cachefs_req_cache, KM_SLEEP);
rp->cfs_cmd = CFS_CACHE_SYNC;
rp->cfs_cr = cr;
rp->cfs_req_u.cu_fs_sync.cf_cachep = cachep;
crhold(rp->cfs_cr);
cachefs_addqueue(rp, &cachep->c_workq);
}
/*ARGSUSED*/
static int
cachefs_sync(vfs_t *vfsp, short flag, cred_t *cr)
{
struct fscache *fscp;
struct cachefscache *cachep;
if (getzoneid() != GLOBAL_ZONEID)
return (EPERM);
if (!(flag & SYNC_ATTR)) {
/*
* queue an async request to do the sync.
* We always sync an entire cache (as opposed to an
* individual fscache) so that we have an opportunity
* to set the clean flag.
*/
if (vfsp) {
/*LINTED alignment okay*/
fscp = (struct fscache *)vfsp->vfs_data;
queue_sync(fscp->fs_cache, cr);
} else {
mutex_enter(&cachefs_cachelock);
for (cachep = cachefs_cachelist; cachep != NULL;
cachep = cachep->c_next) {
queue_sync(cachep, cr);
}
mutex_exit(&cachefs_cachelock);
}
}
return (0);
}
static int
cachefs_remount(struct vfs *vfsp, struct mounta *uap)
{
fscache_t *fscp = VFS_TO_FSCACHE(vfsp);
cachefscache_t *cachep = fscp->fs_cache;
int error = 0;
STRUCT_DECL(cachefs_mountargs, map);
struct cachefsoptions *cfs_options;
char *backfs, *cacheid, *cachedir;
struct vnode *cachedirvp = NULL;
ino64_t fsid;
vnode_t *backrootvp = NULL;
struct vnode *tmpdirvp = NULL;
STRUCT_INIT(map, get_udatamodel());
error = copyin(uap->dataptr, STRUCT_BUF(map),
SIZEOF_STRUCT(cachefs_mountargs, DATAMODEL_NATIVE));
if (error)
goto out;
/*
* get cache directory vp
*/
cachedir = (char *)STRUCT_FGETP(map, cfs_cachedir);
error = lookupname(cachedir, UIO_USERSPACE, FOLLOW,
NULLVPP, &cachedirvp);
if (error)
goto out;
if (cachedirvp->v_type != VDIR) {
error = EINVAL;
goto out;
}
error = 0;
if (cachedirvp) {
error = VOP_LOOKUP(cachedirvp, CACHEFS_DLOG_FILE,
&tmpdirvp, NULL, 0, NULL, kcred);
}
cfs_options = (struct cachefsoptions *)STRUCT_FADDR(map, cfs_options);
cacheid = (char *)STRUCT_FGETP(map, cfs_cacheid);
/* XXX not quite right */
#if 0
/*
* If a log file exists and the cache is being mounted without
* the snr (aka disconnectable) option, return an error.
*/
if ((error == 0) &&
!(cfs_options->opt_flags & CFS_DISCONNECTABLE)) {
cmn_err(CE_WARN,
"cachefs_mount: log exists and disconnectable"
"option not specified\n");
error = EINVAL;
goto out;
}
#endif
error = 0;
/*
* If the user is using NFSv4 and there are other options
* specified, make sure we ignore the other options.
*/
if (CFS_ISFS_BACKFS_NFSV4(fscp)) {
cfs_options->opt_flags = CFS_BACKFS_NFSV4;
}
/* XXX need mount options "nocache" and "nofill" */
/* if nocache is being turned off */
if (cachep->c_flags & CACHE_NOCACHE) {
error = cachefs_cache_activate_ro(cachep, cachedirvp);
if (error)
goto out;
cachefs_cache_activate_rw(cachep);
/* get the fsid for the fscache */
error = fscache_name_to_fsid(cachep, cacheid, &fsid);
if (error)
fsid = 0;
/* activate the fscache */
mutex_enter(&cachep->c_fslistlock);
error = fscache_enable(fscp, fsid, cacheid,
cfs_options, fscp->fs_info.fi_root);
mutex_exit(&cachep->c_fslistlock);
if (error) {
cmn_err(CE_WARN, "cachefs: cannot remount %s\n",
cacheid);
goto out;
}
/* enable the cache */
cachefs_enable_caching(fscp);
fscache_activate_rw(fscp);
}
/* else if nofill is being turn off */
else if (cachep->c_flags & CACHE_NOFILL) {
ASSERT(cachep->c_flags & CACHE_NOFILL);
cachefs_cache_activate_rw(cachep);
/* enable the cache */
cachefs_enable_caching(fscp);
fscache_activate_rw(fscp);
}
fscache_acset(fscp, STRUCT_FGET(map, cfs_acregmin),
STRUCT_FGET(map, cfs_acregmax),
STRUCT_FGET(map, cfs_acdirmin), STRUCT_FGET(map, cfs_acdirmax));
/* if the backfs is mounted now or we have a new backfs */
backfs = (char *)STRUCT_FGETP(map, cfs_backfs);
if (backfs && (cfs_options->opt_flags & CFS_SLIDE)) {
/* get the back file system root vp */
error = lookupname(backfs, UIO_USERSPACE, FOLLOW,
NULLVPP, &backrootvp);
if (error)
goto out;
/*
* Make sure the thing we just looked up is a directory
* and a root of a file system
*/
if (backrootvp->v_type != VDIR ||
!(backrootvp->v_flag & VROOT)) {
cmn_err(CE_WARN,
"cachefs_mount: backpath not a directory\n");
error = EINVAL;
goto out;
}
/*
* XXX
* Kind of dangerous to just set this but we do
* not have locks around usage of fs_backvfsp.
* Hope for the best for now.
* Probably should also spin through vnodes and fix them up.
* Krishna - fixed c_backvp to reflect the change.
*/
fscp->fs_backvfsp = backrootvp->v_vfsp;
((cnode_t *)(fscp->fs_rootvp->v_data))->c_backvp = backrootvp;
/*
* Now the root cnode structure is an owner of
* the opened back root vnode structure; we must
* clear the pointer to back root vnode here as
* we don't need it since now, and the root cnode
* structure will control the vnode
*/
backrootvp = (vnode_t *)NULL;
}
if (fscp->fs_kstat_id > 0)
cachefs_kstat_umount(fscp->fs_kstat_id);
fscp->fs_kstat_id = 0;
cachefs_kstat_mount(fscp, uap->dir, backfs, cachedir, cacheid);
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_MOUNT))
cachefs_log_mount(cachep, error, vfsp, fscp,
uap->dir, UIO_USERSPACE,
(STRUCT_BUF(map) != NULL) ? cacheid : NULL);
out:
if (cachedirvp)
VN_RELE(cachedirvp);
if (backrootvp)
VN_RELE(backrootvp);
return (error);
}