4570 lines
131 KiB
C
4570 lines
131 KiB
C
/*
|
|
* Copyright (c) 1992, Sun Microsystems, Inc.
|
|
* All rights reserved.
|
|
#pragma ident "@(#)cachefs_subr.c 1.110 94/04/21 SMI"
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/cred.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/pathname.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <sys/kmem.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/debug.h>
|
|
#include <sys/cmn_err.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/pfdat.h>
|
|
#include <sys/dnlc.h>
|
|
#include <sys/dirent.h>
|
|
#include <sys/acct.h>
|
|
#include <sys/runq.h>
|
|
#include <ksys/vproc.h>
|
|
#include <sys/uthread.h>
|
|
#include "os/proc/pproc_private.h" /* XXX bogus */
|
|
|
|
#include "cachefs_fs.h"
|
|
#include <sys/siginfo.h>
|
|
#include <sys/ksignal.h>
|
|
#include <ksys/vfile.h>
|
|
#include <ksys/fdt.h>
|
|
#include <sys/kthread.h>
|
|
#include <sys/sched.h>
|
|
#ifdef DEBUG
|
|
#include <sys/idbg.h>
|
|
#include <sys/idbgentry.h>
|
|
#include <string.h>
|
|
#include <sys/var.h>
|
|
#endif /* DEBUG */
|
|
|
|
|
|
|
|
extern cachefscache_t *cachefs_cachelist;
|
|
int cachefsio_write( struct cnode *, struct buf *, cred_t * );
|
|
int cachefsio_read( struct cnode *, struct buf *, cred_t * );
|
|
|
|
ino_t cachefs_check_fileno = 0;
|
|
|
|
|
|
/*
|
|
* Cache routines
|
|
*/
|
|
|
|
cachefscache_t *
|
|
cachefscache_list_find(char *name)
|
|
{
|
|
cachefscache_t *cachep;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefscache_list_find,
|
|
current_pid(), name, 0);
|
|
ASSERT(VALID_ADDR(name));
|
|
for (cachep = cachefs_cachelist; cachep != NULL;
|
|
cachep = cachep->c_next) {
|
|
if ( strcmp(cachep->c_name, name) == 0 )
|
|
break;
|
|
}
|
|
return(cachep);
|
|
}
|
|
|
|
int
|
|
activate_cache(cachefscache_t *cachep)
|
|
{
|
|
int error;
|
|
|
|
if (cachep->c_refcnt) {
|
|
return(0);
|
|
}
|
|
ASSERT(!cachep->c_dirvp);
|
|
ASSERT(!cachep->c_labelvp);
|
|
ASSERT(cachep->c_name && VALID_ADDR(cachep->c_name));
|
|
error = lookupname(cachep->c_name, UIO_SYSSPACE, FOLLOW, NULLVPP,
|
|
&cachep->c_dirvp, NULL);
|
|
if (!error) {
|
|
VOP_LOOKUP(cachep->c_dirvp, CACHELABEL_NAME, &cachep->c_labelvp, NULL,
|
|
0, NULL, kcred, error);
|
|
if (!error) {
|
|
error = cachefs_async_start(cachep);
|
|
if (!error) {
|
|
return(0);
|
|
} else {
|
|
cmn_err(CE_WARN, "CacheFS: unable to start async daemon for "
|
|
"cache %s, error %d\n", cachep->c_name, error);
|
|
}
|
|
} else {
|
|
cmn_err(CE_WARN,
|
|
"CacheFS: unable to find cache %s label %s, error %d\n",
|
|
cachep->c_name, CACHELABEL_NAME, error);
|
|
}
|
|
} else {
|
|
cmn_err(CE_WARN, "CacheFS: unable to find cache %s, error %d\n",
|
|
cachep->c_name, error);
|
|
}
|
|
if (cachep->c_dirvp) {
|
|
VN_RELE(cachep->c_dirvp);
|
|
}
|
|
if (cachep->c_labelvp) {
|
|
VN_RELE(cachep->c_labelvp);
|
|
}
|
|
cachep->c_dirvp = cachep->c_labelvp = NULL;
|
|
return(error);
|
|
}
|
|
|
|
void
|
|
deactivate_cache(cachefscache_t *cachep)
|
|
{
|
|
VN_RELE(cachep->c_dirvp);
|
|
VN_RELE(cachep->c_labelvp);
|
|
cachep->c_dirvp = cachep->c_labelvp = NULL;
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------------
|
|
*
|
|
* cachefs_cache_create
|
|
*
|
|
* Description:
|
|
* Creates a cachefscache_t object and initializes it to
|
|
* be NOCACHE and NOFILL mode.
|
|
* Arguments:
|
|
* Returns:
|
|
* Returns a pointer to the created object or NULL if
|
|
* threads could not be created.
|
|
* Preconditions:
|
|
*/
|
|
|
|
int
|
|
cachefs_cache_create(char **name, cachefscache_t **cachep, vnode_t *cdvp)
|
|
{
|
|
u_int metasize = (sizeof(cachefsmeta_t) + sizeof(int) - 1) / sizeof(int);
|
|
int error = 0;
|
|
vnode_t *labelvp = NULL;
|
|
vattr_t *attr = NULL;
|
|
uio_t uio;
|
|
iovec_t iov;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_cache_create: ENTER name %s\n", *name));
|
|
ASSERT(VALID_ADDR(name));
|
|
ASSERT(VALID_ADDR(*name));
|
|
ASSERT(VALID_ADDR(cachep));
|
|
ASSERT(VALID_ADDR(cdvp));
|
|
ASSERT(cdvp->v_count >= 1);
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_cache_create,
|
|
current_pid(), name, cachep);
|
|
/* allocate zeroed memory for the object */
|
|
*cachep = (cachefscache_t *)CACHEFS_KMEM_ZALLOC(sizeof (cachefscache_t),
|
|
/*LINTED alignment okay*/
|
|
KM_SLEEP);
|
|
|
|
spinlock_init(&(*cachep)->c_contentslock, "cachefscache lock");
|
|
mutex_init(&(*cachep)->c_fslistlock, MUTEX_DEFAULT, "cache fslist");
|
|
sv_init(&(*cachep)->c_replacement_sv, SV_DEFAULT, "cachefs replacement sv");
|
|
sv_init(&(*cachep)->c_repwait_sv, SV_DEFAULT, "cachefs repwait cv");
|
|
#if defined(DEBUG) && defined(_CACHE_TRACE)
|
|
spinlock_init(&(*cachep)->c_trace.ct_lock, "cache trace lock");
|
|
#endif /* DEBUG && _CACHE_TRACE */
|
|
|
|
/* set up the work queue and get the sync thread created */
|
|
cachefs_workq_init(&(*cachep)->c_workq);
|
|
if (error = cachefs_async_start(*cachep)) {
|
|
goto out;
|
|
}
|
|
|
|
(*cachep)->c_flags = 0;
|
|
(*cachep)->c_name = *name;
|
|
*name = NULL;
|
|
|
|
attr = (vattr_t *)CACHEFS_ZONE_ALLOC(Cachefs_attr_zone, KM_SLEEP);
|
|
/* get the mode bits of the cache directory */
|
|
attr->va_mask = AT_ALL;
|
|
VOP_GETATTR(cdvp, attr, 0, kcred, error);
|
|
if (error)
|
|
goto out;
|
|
|
|
/* ensure the mode bits are 000 to keep out casual users */
|
|
if (attr->va_mode & S_IAMB) {
|
|
cmn_err_tag(133,CE_WARN, "cachefs: Cache Directory Mode must be 000\n");
|
|
error = EPERM;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* record the block size for the front file system
|
|
* set the block size to be no smaller than a page
|
|
*/
|
|
(*cachep)->c_bsize = attr->va_blksize;
|
|
(*cachep)->c_bmapsize = MAX((*cachep)->c_bsize, NBPP);
|
|
/*
|
|
* If the front FS block size is not a power of two, calculate the bmap
|
|
* size to be the nearest power of 2 less than or equal to the front FS
|
|
* block size.
|
|
*
|
|
* For any integer k, k & (k - 1) will be 0 if and only if k = 2**j for
|
|
* some integer j.
|
|
*/
|
|
if (attr->va_blksize & (attr->va_blksize - 1)) {
|
|
(*cachep)->c_bmapsize = 1;
|
|
while ((*cachep)->c_bsize >= ((*cachep)->c_bmapsize << 1)) {
|
|
(*cachep)->c_bmapsize <<= 1;
|
|
}
|
|
}
|
|
ASSERT((*cachep)->c_bmapsize);
|
|
|
|
/* Get the label file */
|
|
VOP_LOOKUP(cdvp, CACHELABEL_NAME, &labelvp, NULL, 0, NULL,
|
|
kcred, error);
|
|
if (error) {
|
|
cmn_err(CE_WARN,
|
|
"CacheFS: unable to open label file %s for cache %s: error %d.\n",
|
|
CACHELABEL_NAME, (*cachep)->c_name, error);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* read in the label
|
|
* assume that the mount process has already locked the label file
|
|
*/
|
|
UIO_SETUP(&uio, &iov, (caddr_t)&(*cachep)->c_label,
|
|
sizeof (struct cache_label), (off_t)0, UIO_SYSSPACE, FREAD, (off_t)0);
|
|
READ_VP(labelvp, &uio, 0, kcred, &curuthread->ut_flid, error);
|
|
if (error) {
|
|
cmn_err(CE_WARN,
|
|
"CacheFS: error %d when reading cache label %s for cache %s.\n",
|
|
error, CACHELABEL_NAME, (*cachep)->c_name);
|
|
goto out;
|
|
} else if (uio.uio_resid) {
|
|
error = EIO;
|
|
cmn_err(CE_WARN,
|
|
"cachefs: incomplete cache label for cache %s.\n",
|
|
(*cachep)->c_name);
|
|
goto out;
|
|
}
|
|
|
|
/* Verify that we can handle the version this cache was created under */
|
|
/*
|
|
* If the mode has not been set, this is a newly created label file.
|
|
* In this case, complete the version number by filling in the size
|
|
* of a long and the size of the cachefs metadata structure. This
|
|
* must be done in the kernel because it is the kernel that creates
|
|
* the cache files. In particular, each file has a vattr_t on the
|
|
* front.
|
|
*/
|
|
if (((*cachep)->c_label.cl_cfsversion == CFSVERSION) &&
|
|
!(*cachep)->c_label.cl_cfslongsize &&
|
|
!(*cachep)->c_label.cl_cfsmetasize) {
|
|
/*
|
|
* fill in the mode portion of the version
|
|
* mode is the size of long
|
|
* assume that the label file has been locked as writer
|
|
*/
|
|
(*cachep)->c_label.cl_cfsversion = CFSVERSION;
|
|
(*cachep)->c_label.cl_cfslongsize = sizeof(long);
|
|
(*cachep)->c_label.cl_cfsmetasize = metasize;
|
|
/*
|
|
* write out the label
|
|
*/
|
|
UIO_SETUP(&uio, &iov, (caddr_t)&(*cachep)->c_label,
|
|
sizeof(struct cache_label), (off_t)0, UIO_SYSSPACE, FWRITE,
|
|
(off_t)RLIM_INFINITY);
|
|
WRITE_VP(labelvp, &uio, 0, kcred, &curuthread->ut_flid,
|
|
error);
|
|
if (error) {
|
|
cmn_err(CE_WARN,
|
|
"CacheFS: Can't write Cache Label File for cache %s, "
|
|
"error %d\n", (*cachep)->c_name, error);
|
|
ASSERT(uio.uio_sigpipe == 0);
|
|
goto out;
|
|
}
|
|
} else if ((*cachep)->c_label.cl_cfsversion != CFSVERSION) {
|
|
cmn_err(CE_WARN,
|
|
"CacheFS: Invalid Cache Version for cache %s: %d, expected %d\n",
|
|
(*cachep)->c_name, (*cachep)->c_label.cl_cfsversion, CFSVERSION);
|
|
error = EINVAL;
|
|
goto out;
|
|
} else if ((*cachep)->c_label.cl_cfslongsize != sizeof(long)) {
|
|
cmn_err(CE_WARN,
|
|
"CacheFS: Invalid Cache Long Size for cache %s: %d, expected %d\n",
|
|
(*cachep)->c_name, (*cachep)->c_label.cl_cfslongsize, sizeof(long));
|
|
error = EINVAL;
|
|
goto out;
|
|
} else if ((*cachep)->c_label.cl_cfsmetasize != metasize) {
|
|
cmn_err(CE_WARN,
|
|
"CacheFS: Invalid Cache Attribute Size for cache %s: %d, "
|
|
"expected %d\n", (*cachep)->c_name,
|
|
(*cachep)->c_label.cl_cfsmetasize,
|
|
metasize);
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
VN_HOLD(cdvp);
|
|
(*cachep)->c_dirvp = cdvp;
|
|
(*cachep)->c_labelvp = labelvp;
|
|
labelvp = NULL;
|
|
out:
|
|
if (attr)
|
|
CACHEFS_ZONE_FREE(Cachefs_attr_zone, attr);
|
|
if (labelvp != NULL) {
|
|
VN_RELE(labelvp);
|
|
}
|
|
if (error) {
|
|
cachefs_cache_destroy(*cachep);
|
|
*cachep = NULL;
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_cache_create: EXIT error %d\n", error));
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------------
|
|
*
|
|
* cachefs_cache_destroy
|
|
*
|
|
* Description:
|
|
* Destroys the cachefscache_t object.
|
|
* Arguments:
|
|
* cachep the cachefscache_t object to destroy
|
|
* Returns:
|
|
* Preconditions:
|
|
* precond(cachep)
|
|
*/
|
|
|
|
void
|
|
cachefs_cache_destroy(cachefscache_t *cachep)
|
|
{
|
|
int error;
|
|
struct flock fl;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_cache_destroy: ENTER cachep 0x%p\n", cachep));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_cache_destroy,
|
|
current_pid(), cachep, 0);
|
|
ASSERT(VALID_ADDR(cachep));
|
|
/* stop async threads */
|
|
while (cachep->c_workq.wq_thread_count > 0)
|
|
(void) cachefs_async_halt(&cachep->c_workq);
|
|
|
|
/* stop the garbage collection thread */
|
|
cachefs_replacement_halt(cachep);
|
|
|
|
ASSERT(cachep->c_fslist == NULL);
|
|
ASSERT(cachep->c_refcnt == 0);
|
|
|
|
if (cachep->c_labelvp) {
|
|
/* unlock the label file */
|
|
fl.l_type = F_UNLCK;
|
|
fl.l_whence = 0;
|
|
fl.l_start = 0;
|
|
fl.l_len = 0;
|
|
fl.l_sysid = 0;
|
|
fl.l_pid = 0;
|
|
VOP_FRLOCK(cachep->c_labelvp, F_SETLK, &fl, FREAD, (off_t)0,
|
|
VRWLOCK_NONE, sys_cred, error);
|
|
if (error) {
|
|
cmn_err(CE_WARN,
|
|
"CacheFS: Can't unlock lock label file, error %d\n", error);
|
|
}
|
|
VN_RELE(cachep->c_labelvp);
|
|
}
|
|
|
|
if (cachep->c_dirvp)
|
|
VN_RELE(cachep->c_dirvp);
|
|
|
|
if (cachep->c_name)
|
|
CACHEFS_ZONE_FREE(Cachefs_path_zone, cachep->c_name);
|
|
spinlock_destroy(&cachep->c_contentslock);
|
|
mutex_destroy(&cachep->c_fslistlock);
|
|
sv_destroy(&cachep->c_replacement_sv);
|
|
sv_destroy(&cachep->c_repwait_sv);
|
|
#if defined(DEBUG) && defined(_CACHE_TRACE)
|
|
spinlock_destroy(&(*cachep)->c_trace.ct_lock);
|
|
#endif /* DEBUG && _CACHE_TRACE */
|
|
|
|
/* destroy the work queue */
|
|
cachefs_workq_destroy(&cachep->c_workq);
|
|
bzero((caddr_t)cachep, sizeof (cachefscache_t));
|
|
CACHEFS_KMEM_FREE(cachep, sizeof (cachefscache_t));
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_cache_destroy: EXIT\n"));
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------------
|
|
*
|
|
* cachefs_cache_sync
|
|
*
|
|
* Description:
|
|
* Sync a cache which includes all of its fscaches.
|
|
* Arguments:
|
|
* cachep the cachefscache_t object to sync
|
|
* Returns:
|
|
* Preconditions:
|
|
* precond(cachep)
|
|
* precond(cache is in rw mode)
|
|
*/
|
|
|
|
void
|
|
cachefs_cache_sync(struct cachefscache *cachep)
|
|
{
|
|
struct fscache *fscp;
|
|
struct fscache **syncfsc;
|
|
int nfscs, fscidx;
|
|
int alloc_count;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_cache_sync: ENTER cachep 0x%p\n", cachep));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_cache_sync,
|
|
current_pid(), cachep, 0);
|
|
ASSERT(VALID_ADDR(cachep));
|
|
|
|
nfscs = 0;
|
|
|
|
mutex_enter(&cachep->c_fslistlock);
|
|
if (cachep->c_refcnt) {
|
|
alloc_count = cachep->c_refcnt * sizeof(struct fscache *);
|
|
syncfsc = (struct fscache **) CACHEFS_KMEM_ALLOC(alloc_count,
|
|
/*LINTED alignment okay*/
|
|
KM_SLEEP);
|
|
for (fscp = cachep->c_fslist; fscp; fscp = fscp->fs_next) {
|
|
if (!fscp->fs_sync_running) {
|
|
fscp->fs_sync_running = 1;
|
|
fscache_hold(fscp);
|
|
ASSERT(nfscs < cachep->c_refcnt);
|
|
syncfsc[nfscs++] = fscp;
|
|
}
|
|
}
|
|
ASSERT(nfscs <= cachep->c_refcnt);
|
|
mutex_exit(&cachep->c_fslistlock);
|
|
for (fscidx = 0; fscidx < nfscs; fscidx++) {
|
|
fscp = syncfsc[fscidx];
|
|
fscache_sync(fscp, SYNC_NOWAIT);
|
|
fscache_rele(fscp);
|
|
mutex_enter(&cachep->c_fslistlock);
|
|
fscp->fs_sync_running = 0;
|
|
sv_broadcast(&fscp->fs_sync_wait);
|
|
mutex_exit(&cachep->c_fslistlock);
|
|
}
|
|
CACHEFS_KMEM_FREE(syncfsc, alloc_count);
|
|
} else {
|
|
mutex_exit(&cachep->c_fslistlock);
|
|
}
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_cache_sync: EXIT\n"));
|
|
}
|
|
|
|
/*
|
|
* Invalidate the cached object. This consists of flushing and invalidating
|
|
* all buffers, clearing the file header state and allocation map, and
|
|
* calling the consistency mechanism to invalidate the object.
|
|
* If buffers are to be invalidated, exclusive access to the object is
|
|
* required. This function will release and recuire c_rwlock if necessary.
|
|
* As a result, this function can only be used at the beginning or the
|
|
* end of a critical section covered by c_rwlock.
|
|
*
|
|
* c_rwlock will be left in the state (RW_READER or RW_WRITER) in which
|
|
* it was held by the caller.
|
|
*/
|
|
int
|
|
cachefs_inval_object(cnode_t *cp, lockmode_t lm)
|
|
{
|
|
int error = 0;
|
|
int ospl;
|
|
vnode_t *vp = NULL;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR | CFSDEBUG_INVAL,
|
|
printf("cachefs_inval_object: ENTER cp %p\n", cp));
|
|
ASSERT(cp);
|
|
ASSERT(CTOV(cp));
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_inval_object,
|
|
current_pid(), cp, 0);
|
|
ASSERT(VALID_ADDR(cp));
|
|
CACHEFS_STATS->cs_inval++;
|
|
|
|
vp = CTOV(cp);
|
|
|
|
/*
|
|
* For regular files, we will be invalidating buffers, so we'll need
|
|
* exclusive access to the code to prevent another process from
|
|
* creating buffers while we're invalidating them.
|
|
* For all file types, we'll be truncating the front file, so we'll
|
|
* need exclusive access.
|
|
*/
|
|
switch (lm) {
|
|
case READLOCKED: /* read lock held */
|
|
ASSERT(RW_READ_HELD(&cp->c_rwlock));
|
|
rw_exit(&cp->c_rwlock);
|
|
rw_enter(&cp->c_rwlock, RW_WRITER);
|
|
break;
|
|
case UNLOCKED: /* lock needed and none is held */
|
|
rw_enter(&cp->c_rwlock, RW_WRITER);
|
|
break;
|
|
case WRITELOCKED: /* write lock held */
|
|
ASSERT(RW_WRITE_HELD(&cp->c_rwlock));
|
|
case NOLOCK: /* no lock is required */
|
|
break;
|
|
}
|
|
|
|
ASSERT((lm == NOLOCK) || RW_WRITE_HELD(&cp->c_rwlock));
|
|
CNODE_TRACE(CNTRACE_INVAL, cp, (void *)__return_address,
|
|
(int)lm, 0);
|
|
/*
|
|
* Prevent recursive and multiple invalidations.
|
|
*/
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
if (cp->c_flags & CN_INVAL) {
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
goto out2;
|
|
}
|
|
cp->c_flags |= CN_INVAL;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
switch (vp->v_type) {
|
|
case VREG:
|
|
/*
|
|
* Invalidate any buffers for the file.
|
|
*/
|
|
error = cachefs_flushvp(CTOBHV(cp), (off_t)0, 0, CFS_INVAL, 0);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
break;
|
|
case VDIR:
|
|
/*
|
|
* purge entries from the DNLC
|
|
*/
|
|
dnlc_purge_vp(vp);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (C_CACHING(cp)) {
|
|
ASSERT(cp->c_fileheader);
|
|
/*
|
|
* De-allocate any space used
|
|
*/
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_fileheader->fh_metadata.md_state &=
|
|
(MD_MDVALID | MD_KEEP | MD_NOTRUNC);
|
|
cp->c_fileheader->fh_metadata.md_allocents = 0;
|
|
CNODE_TRACE(CNTRACE_ALLOCENTS, cp, cachefs_inval_object,
|
|
(int)cp->c_fileheader->fh_metadata.md_allocents, 0);
|
|
cp->c_flags |= CN_UPDATED;
|
|
CNODE_TRACE(CNTRACE_UPDATE, cp, (void *)cachefs_inval_object,
|
|
cp->c_flags, cp->c_fileheader->fh_metadata.md_allocents);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
if (!cp->c_frontvp) {
|
|
error = cachefs_getfrontvp(cp);
|
|
CFS_DEBUG(CFSDEBUG_ERROR, if (error)
|
|
printf("cachefs_inval_object(line %d): error %d "
|
|
"getting front vnode\n", __LINE__, error));
|
|
if (error) {
|
|
(void)cachefs_nocache(cp);
|
|
error = 0;
|
|
}
|
|
}
|
|
ASSERT(cachefs_zone_validate((caddr_t)cp->c_fileheader,
|
|
FILEHEADER_BLOCK_SIZE(C_TO_FSCACHE(cp)->fs_cache)));
|
|
ASSERT(valid_file_header(cp->c_fileheader, NULL));
|
|
/*
|
|
* Remove the cache front file's contents except for the
|
|
* file header.
|
|
*/
|
|
error = FRONT_VOP_SETATTR(cp, &Cachefs_file_attr, 0, sys_cred);
|
|
if (error) {
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags |= CN_NOCACHE;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefs_inval_object,
|
|
(int)cp->c_flags, 0);
|
|
CFS_DEBUG(CFSDEBUG_ERROR | CFSDEBUG_NOCACHE,
|
|
printf("cachefs_inval_object: error %d truncating front "
|
|
"file, marking file as uncached\n", error));
|
|
error = 0;
|
|
}
|
|
}
|
|
/*
|
|
* Call the consistency mechanism to expire the object.
|
|
*/
|
|
CFSOP_EXPIRE_COBJECT(C_TO_FSCACHE(cp), cp, sys_cred);
|
|
|
|
out:
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags &= ~(CN_INVAL | CN_NEEDINVAL);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
|
|
out2:
|
|
/*
|
|
* Restore the lock to its original state.
|
|
*/
|
|
switch (lm) {
|
|
case READLOCKED: /* read lock held */
|
|
rw_downgrade(&cp->c_rwlock);
|
|
break;
|
|
case UNLOCKED: /* lock needed and none is held */
|
|
rw_exit(&cp->c_rwlock);
|
|
break;
|
|
case WRITELOCKED: /* write lock held */
|
|
case NOLOCK: /* no lock is reuired */
|
|
break;
|
|
}
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR | CFSDEBUG_INVAL,
|
|
printf("cachefs_inval_object: EXIT error %d\n", error));
|
|
return( error );
|
|
}
|
|
|
|
int
|
|
cachefs_nocache(cnode_t *cp)
|
|
{
|
|
int error = 0;
|
|
int ospl;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_nocache: ENTER cp %p\n", cp));
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_nocache, current_pid(),
|
|
cp, 0);
|
|
ASSERT(VALID_ADDR(cp));
|
|
CACHEFS_STATS->cs_nocache++;
|
|
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags |= (CN_NOCACHE | CN_NEEDINVAL);
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefs_nocache, (int)cp->c_flags, 0);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_nocache: EXIT error %d\n", error));
|
|
return(error);
|
|
}
|
|
|
|
static off_t
|
|
fix_offsets(char *dirbuf, ssize_t buflen, off_t startoff)
|
|
{
|
|
dirent_t *dep;
|
|
char *bp = dirbuf;
|
|
off_t lastoff = startoff;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)fix_offsets, current_pid(),
|
|
dirbuf, buflen);
|
|
ASSERT(VALID_ADDR(dirbuf));
|
|
while (buflen) {
|
|
dep = (dirent_t *)bp;
|
|
ASSERT(dep->d_reclen);
|
|
ASSERT(dep->d_reclen >= DIRENTSIZE(1));
|
|
lastoff = dep->d_off = ++startoff;
|
|
bp += dep->d_reclen;
|
|
buflen -= dep->d_reclen;
|
|
}
|
|
return(lastoff);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/*
|
|
* Validate a buffer full of directory entries.
|
|
*/
|
|
int
|
|
valid_dirents( dirent_t *dep, int dircount )
|
|
{
|
|
int good = 1;
|
|
dirent_t *nextdep = NULL;
|
|
|
|
ASSERT(VALID_ADDR(dep));
|
|
ASSERT(VALID_ADDR((u_long)dep + (u_long)dircount));
|
|
while (dircount && (dircount >= (int)DIRENTSIZE(1)) &&
|
|
(dircount >= dep->d_reclen)) {
|
|
if ((dep->d_reclen == 0) || (dep->d_reclen < DIRENTSIZE(1))) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("valid_dirents: bad directory entry: dep 0x%p "
|
|
"reclen %d dircount %d\n", dep,
|
|
(int)dep->d_reclen, dircount));
|
|
good = 0;
|
|
break;
|
|
}
|
|
nextdep = (dirent_t *)((u_long)dep + (u_long)dep->d_reclen);
|
|
dircount -= (int)dep->d_reclen;
|
|
if (dircount && (dircount >= DIRENTSIZE(1)) &&
|
|
((nextdep->d_reclen == 0) ||
|
|
(nextdep->d_reclen < DIRENTSIZE(1)))) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("valid_dirents: next directory entry bad,"
|
|
"dep 0x%p reclen %d nextdep 0x%p reclen %d "
|
|
"dircount %d\n", dep, (int)dep->d_reclen,
|
|
nextdep, (int)nextdep->d_reclen, dircount));
|
|
good = 0;
|
|
break;
|
|
}
|
|
dep = nextdep;
|
|
}
|
|
return(good);
|
|
}
|
|
|
|
static int
|
|
dirent_len(dirent_t *dep, int dircount)
|
|
{
|
|
int len = dircount;
|
|
|
|
ASSERT(VALID_ADDR(dep));
|
|
ASSERT(VALID_ADDR((u_long)dep + (u_long)dircount));
|
|
while (dircount && (dircount >= (int)DIRENTSIZE(1)) &&
|
|
(dircount >= dep->d_reclen)) {
|
|
dircount -= (int)dep->d_reclen;
|
|
dep = (dirent_t *)((u_long)dep + (u_long)dep->d_reclen);
|
|
}
|
|
return(len - dircount);
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
/*
|
|
* Populate a directory on the front file system from the back file system.
|
|
* We only do this population so that readdir will function without having
|
|
* to always go to the back file system. An alternative is to maintain the
|
|
* directory contents in separate files for each directory. This can be
|
|
* maintained in such a way that it can be easily used by readdir. The real
|
|
* directories and files will then be created only as they are needed in
|
|
* lookup.
|
|
*/
|
|
int
|
|
cachefs_populate_dir(cnode_t *cp, cred_t *cr)
|
|
{
|
|
off_t lastoff = (off_t)0;
|
|
int ospl;
|
|
int error = 0;
|
|
uio_t inuio;
|
|
iovec_t iniov;
|
|
uio_t outuio;
|
|
iovec_t outiov;
|
|
char *dirbuf;
|
|
int eof = 0;
|
|
off_t dirsize = (off_t)0;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR | CFSDEBUG_POPDIR,
|
|
printf("cachefs_populate_dir: ENTER cp %p\n", cp));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_populate_dir,
|
|
current_pid(), cp, cr);
|
|
|
|
ASSERT(VALID_ADDR(cp));
|
|
ASSERT(VALID_ADDR(cr));
|
|
ASSERT(CTOV(cp)->v_type == VDIR);
|
|
ASSERT(cp->c_flags & CN_POPULATION_PENDING);
|
|
ASSERT(RW_WRITE_HELD(&cp->c_rwlock));
|
|
ASSERT(!issplhi(getsr()));
|
|
|
|
if (!C_CACHING(cp)) {
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags &= ~CN_POPULATION_PENDING;
|
|
sv_broadcast(&cp->c_popwait_sv);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
return(0);
|
|
}
|
|
ASSERT(cp->c_fileheader);
|
|
/*
|
|
* Truncate the front file before we update it from the back file.
|
|
* This is because the front file may shrink.
|
|
* We want to truncate the front file but only expire the object.
|
|
*/
|
|
error = cachefs_inval_object(cp, WRITELOCKED);
|
|
if (error) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_populate_dir(line %d): error truncating "
|
|
"front directory file: %d\n", __LINE__, error));
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags &= ~CN_POPULATION_PENDING;
|
|
sv_broadcast(&cp->c_popwait_sv);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefs_populate_dir: nocache cp 0x%p on error %d "
|
|
"from cachefs_inval_object\n", cp, error));
|
|
cachefs_nocache(cp);
|
|
return(error);
|
|
}
|
|
dirbuf = CACHEFS_KMEM_ALLOC(CFS_BMAPSIZE(cp), KM_SLEEP);
|
|
UIO_SETUP(&inuio, &iniov, dirbuf, CFS_BMAPSIZE(cp), (off_t)0,
|
|
UIO_SYSSPACE, 0, RLIM_INFINITY);
|
|
/*
|
|
* The directory data starts after the file header. Write out
|
|
* as much as was read into the buffer.
|
|
*/
|
|
UIO_SETUP(&outuio, &outiov, dirbuf, CFS_BMAPSIZE(cp),
|
|
(off_t)DATA_START(C_TO_FSCACHE(cp)), UIO_SYSSPACE, 0, RLIM_INFINITY);
|
|
ASSERT(!issplhi(getsr()));
|
|
do {
|
|
(void)BACK_VOP_READDIR(cp, &inuio, cr, &eof, BACKOP_BLOCK, &error);
|
|
if (error) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_populate_dir(line %d): error reading "
|
|
"back directory: %d\n", __LINE__, error));
|
|
break;
|
|
}
|
|
/*
|
|
* Determin how much was read, if any.
|
|
*/
|
|
outuio.uio_resid = outiov.iov_len = CFS_BMAPSIZE(cp) - inuio.uio_resid;
|
|
if (outiov.iov_len) {
|
|
dirsize += outiov.iov_len;
|
|
CFS_DEBUG(CFSDEBUG_POPDIR,
|
|
printf("cachefs_populate_dir: read %d bytes, dirsize %d, "
|
|
"write offset 0x%llx next offset 0x%llx\n",
|
|
(int) outiov.iov_len, (int)dirsize,
|
|
outuio.uio_offset, inuio.uio_offset));
|
|
ASSERT(valid_dirents((dirent_t *)dirbuf, (int)outiov.iov_len));
|
|
lastoff = fix_offsets(dirbuf, outiov.iov_len, lastoff);
|
|
ASSERT(valid_dirents((dirent_t *)dirbuf, (int)outiov.iov_len));
|
|
ASSERT(dirent_len((dirent_t *)dirbuf, (int)outiov.iov_len) ==
|
|
(int)outiov.iov_len);
|
|
error = FRONT_WRITE_VP(cp, &outuio, 0, sys_cred);
|
|
if (error) {
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_populate_dir(line %d): error writing "
|
|
"front directory: %d\n", __LINE__, error));
|
|
if (error != EINTR) {
|
|
/*
|
|
* There is no need to invalidate immediately, as the
|
|
* metadata already indicates that there is no
|
|
* data in the front file.
|
|
*/
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefs_populate_dir: nocache cp 0x%p on "
|
|
"error %d from FRONT_WRITE_VP\n", cp, error));
|
|
cachefs_nocache(cp);
|
|
}
|
|
break;
|
|
} else if (outuio.uio_resid) {
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_populate_dir(line %d): incomplete "
|
|
"write to front directory, resid %d\n", __LINE__,
|
|
(int)outuio.uio_resid));
|
|
/*
|
|
* There is no need to invalidate immediately, as the
|
|
* metadata already indicates that there is no
|
|
* data in the front file.
|
|
*/
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefs_populate_dir: nocache cp 0x%p on "
|
|
"short write, resid %d\n", cp, outuio.uio_resid));
|
|
cachefs_nocache(cp);
|
|
error = EIO;
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
/*
|
|
* Reset the input and output uio and iov structs for another pass.
|
|
* Leave the offset alone.
|
|
*/
|
|
outiov.iov_base = iniov.iov_base = dirbuf;
|
|
inuio.uio_resid = iniov.iov_len = CFS_BMAPSIZE(cp);
|
|
} while (!eof);
|
|
CNODE_TRACE(CNTRACE_POPDIR, cp, cachefs_populate_dir, (int)dirsize, error);
|
|
ASSERT(!issplhi(getsr()));
|
|
CACHEFS_KMEM_FREE(dirbuf, CFS_BMAPSIZE(cp));
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags &= ~CN_POPULATION_PENDING;
|
|
if (!error) {
|
|
cp->c_fileheader->fh_metadata.md_state |= MD_POPULATED;
|
|
cp->c_fileheader->fh_metadata.md_allocents = 1;
|
|
CNODE_TRACE(CNTRACE_ALLOCENTS, cp, cachefs_populate_dir,
|
|
(int)cp->c_fileheader->fh_metadata.md_allocents, 0);
|
|
cp->c_fileheader->fh_allocmap[0].am_start_off = (off_t)0;
|
|
cp->c_fileheader->fh_allocmap[0].am_size = dirsize;
|
|
CNODE_TRACE(CNTRACE_ALLOCMAP, cp, cachefs_populate_dir, 0,
|
|
(int)cp->c_fileheader->fh_allocmap[0].am_size);
|
|
ASSERT(dirsize > 0);
|
|
cp->c_flags |= CN_UPDATED;
|
|
CFS_DEBUG(CFSDEBUG_POPDIR, ASSERT(valid_allocmap));
|
|
ASSERT(cachefs_zone_validate((caddr_t)cp->c_fileheader,
|
|
FILEHEADER_BLOCK_SIZE(C_TO_FSCACHE(cp)->fs_cache)));
|
|
CNODE_TRACE(CNTRACE_UPDATE, cp, (void *)cachefs_populate_dir,
|
|
cp->c_flags, cp->c_fileheader->fh_metadata.md_allocents);
|
|
ASSERT(valid_file_header(cp->c_fileheader, NULL));
|
|
sv_broadcast(&cp->c_popwait_sv);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
if (!cp->c_frontvp) {
|
|
error = cachefs_getfrontvp(cp);
|
|
CFS_DEBUG(CFSDEBUG_ERROR, if (error)
|
|
printf("cachefs_populate_dir(line %d): error %d "
|
|
"getting front vnode\n", __LINE__, error));
|
|
if (error) {
|
|
(void)cachefs_nocache(cp);
|
|
error = 0;
|
|
}
|
|
}
|
|
} else {
|
|
sv_broadcast(&cp->c_popwait_sv);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
}
|
|
ASSERT(!issplhi(getsr()));
|
|
CFS_DEBUG(CFSDEBUG_SUBR | CFSDEBUG_POPDIR,
|
|
printf("cachefs_populate_dir: EXIT error %d\n", error));
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* Checks to see if the page is in the disk cache, by checking the allocmap.
|
|
*/
|
|
int
|
|
cachefs_check_allocmap(cnode_t *cp, off_t off, off_t size)
|
|
{
|
|
int i;
|
|
off_t size_to_look = MIN(size, cp->c_size - off);
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_check_allocmap,
|
|
current_pid(), cp, off);
|
|
ASSERT(VALID_ADDR(cp));
|
|
ASSERT(C_CACHING(cp));
|
|
ASSERT(cp->c_fileheader);
|
|
for (i = 0; i < cp->c_fileheader->fh_metadata.md_allocents; i++) {
|
|
allocmap_t *allocp = &cp->c_fileheader->fh_allocmap[i];
|
|
|
|
if (off >= allocp->am_start_off) {
|
|
if ((off + size_to_look) <=
|
|
(allocp->am_start_off + allocp->am_size))
|
|
/*
|
|
* Found the page in the CFS disk cache.
|
|
*/
|
|
return (1);
|
|
} else {
|
|
return (0);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
coalesce_allocmap(cnode_t *cp, int ent)
|
|
{
|
|
int i;
|
|
fileheader_t *fhp = cp->c_fileheader;
|
|
|
|
/*
|
|
* We come here when an existing entry has been adjusted to contain
|
|
* the given offset and length. In this case, it may be necessary
|
|
* to coalesce the altered entry with the one immediately following
|
|
* or preceding it.
|
|
* It is not necessarily true that the allocation map is valid at
|
|
* this point in that there may be some adjacent entries. We cannot
|
|
* ASSERT(valid_allocmap(cp)) until after all coalescing has been
|
|
* completed.
|
|
*/
|
|
ASSERT(ent < ALLOCMAP_SIZE);
|
|
ASSERT(ent < fhp->fh_metadata.md_allocents);
|
|
/*
|
|
* Start with the preceeding entry. Obviously, we do this only when
|
|
* there is a preceeding entry (ent > 0).
|
|
*/
|
|
if (ent > 0) {
|
|
/*
|
|
* Check the preceeding entry. Is it adjacent or overlapping?
|
|
*/
|
|
if ((fhp->fh_allocmap[ent - 1].am_start_off +
|
|
fhp->fh_allocmap[ent - 1].am_size) >=
|
|
fhp->fh_allocmap[ent].am_start_off) {
|
|
/*
|
|
* Coalesce with the previous entry by increasing the
|
|
* size of the previous entry so that it contains the
|
|
* entry indexed by ent. Then remove the entry indexed
|
|
* by ent by compressing the allocation map array.
|
|
* If the entry to be removed is at the end of the array,
|
|
* simply decrement the entry count.
|
|
*/
|
|
fhp->fh_allocmap[ent - 1].am_size =
|
|
fhp->fh_allocmap[ent].am_start_off +
|
|
fhp->fh_allocmap[ent].am_size -
|
|
fhp->fh_allocmap[ent - 1].am_start_off;
|
|
for (i = ent + 1; i < fhp->fh_metadata.md_allocents; i++) {
|
|
fhp->fh_allocmap[i - 1] = fhp->fh_allocmap[i];
|
|
}
|
|
fhp->fh_metadata.md_allocents--;
|
|
CNODE_TRACE(CNTRACE_ALLOCENTS, cp, cachefs_update_allocmap,
|
|
fhp->fh_metadata.md_allocents, 0);
|
|
}
|
|
}
|
|
ASSERT(cachefs_zone_validate((caddr_t)fhp,
|
|
FILEHEADER_BLOCK_SIZE(C_TO_FSCACHE(cp)->fs_cache)));
|
|
/*
|
|
* Check the following entry for coalescing. We do this only if there
|
|
* is a following entry. Check this condition by comparing ent + 1
|
|
* to the count of allocation entries. If it is less than this count,
|
|
* it is valid. If it is equal to this count, ent is the index of the
|
|
* last entry.
|
|
*/
|
|
if ((ent + 1) < fhp->fh_metadata.md_allocents) {
|
|
if ((fhp->fh_allocmap[ent].am_start_off +
|
|
fhp->fh_allocmap[ent].am_size) >=
|
|
(fhp->fh_allocmap[ent + 1].am_start_off)) {
|
|
fhp->fh_allocmap[ent].am_size =
|
|
fhp->fh_allocmap[ent + 1].am_start_off +
|
|
fhp->fh_allocmap[ent + 1].am_size -
|
|
fhp->fh_allocmap[ent].am_start_off;
|
|
for (i = ent + 2; i < fhp->fh_metadata.md_allocents; i++) {
|
|
fhp->fh_allocmap[i - 1] = fhp->fh_allocmap[i];
|
|
}
|
|
fhp->fh_metadata.md_allocents--;
|
|
CNODE_TRACE(CNTRACE_ALLOCENTS, cp, cachefs_update_allocmap,
|
|
fhp->fh_metadata.md_allocents, 0);
|
|
}
|
|
}
|
|
cp->c_flags |= CN_UPDATED;
|
|
CNODE_TRACE(CNTRACE_UPDATE, cp, (void *)cachefs_update_allocmap,
|
|
cp->c_flags, fhp->fh_metadata.md_allocents);
|
|
ASSERT(cachefs_zone_validate((caddr_t)fhp,
|
|
FILEHEADER_BLOCK_SIZE(C_TO_FSCACHE(cp)->fs_cache)));
|
|
ASSERT(fhp->fh_metadata.md_allocents <= ALLOCMAP_SIZE);
|
|
ASSERT(valid_file_header(fhp, NULL));
|
|
ASSERT(valid_allocmap(cp));
|
|
}
|
|
|
|
/*
|
|
* Determine the span of the region described by off and size relative
|
|
* to the allocation map entry specified by allocp.
|
|
*
|
|
* There are five regions:
|
|
*
|
|
* S_BEFORE S_START S_MIDDLE S_END S_AFTER
|
|
* 010 020 030 040 050
|
|
* E_BEFORE E_START E_MIDDLE E_END E_AFTER
|
|
* 01 02 03 04 05
|
|
* |-------------------------------|
|
|
*
|
|
* relative to the already locked section. The type is two octal digits,
|
|
* the 8's digit is the start type and the 1's digit is the end type.
|
|
*
|
|
* This code was lifted from regflck in os/flock.c.
|
|
*/
|
|
|
|
/* region types */
|
|
#define S_BEFORE 010 /* starts before */
|
|
#define S_START 020 /* starts at the start */
|
|
#define S_MIDDLE 030 /* starts in the middle */
|
|
#define S_END 040 /* starts at the end */
|
|
#define S_AFTER 050 /* starts after */
|
|
#define E_BEFORE 001 /* ends before */
|
|
#define E_START 002 /* ends at the start */
|
|
#define E_MIDDLE 003 /* ends in the middle */
|
|
#define E_END 004 /* ends at the end */
|
|
#define E_AFTER 005 /* ends after */
|
|
|
|
static int
|
|
cachefs_allocmap_region(allocmap_t *allocp, off_t off, off_t endoff)
|
|
{
|
|
register int regntype;
|
|
off_t allocend = allocp->am_start_off + allocp->am_size - 1;
|
|
|
|
if (off > allocp->am_start_off) {
|
|
if (off - 1 == allocend)
|
|
return S_END|E_AFTER;
|
|
if (off > allocend)
|
|
return S_AFTER|E_AFTER;
|
|
regntype = S_MIDDLE;
|
|
} else if (off == allocp->am_start_off)
|
|
regntype = S_START;
|
|
else
|
|
regntype = S_BEFORE;
|
|
|
|
if (endoff < allocend) {
|
|
if (endoff == allocp->am_start_off - 1)
|
|
regntype |= E_START;
|
|
else if (endoff < allocp->am_start_off)
|
|
regntype |= E_BEFORE;
|
|
else
|
|
regntype |= E_MIDDLE;
|
|
} else if (endoff == allocend)
|
|
regntype |= E_END;
|
|
else
|
|
regntype |= E_AFTER;
|
|
|
|
return regntype;
|
|
}
|
|
|
|
/*
|
|
* Updates the allocmap to reflect a new chunk of data that has been
|
|
* populated.
|
|
*/
|
|
int
|
|
cachefs_update_allocmap(cnode_t *cp, off_t off, off_t size)
|
|
{
|
|
int i;
|
|
int error = 0;
|
|
int ent;
|
|
allocmap_t *allocp;
|
|
off_t endoff = off + size;
|
|
fileheader_t *fhp = cp->c_fileheader;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_update_allocmap,
|
|
current_pid(), cp, off);
|
|
ASSERT(VALID_ADDR(cp));
|
|
ASSERT(cp->c_fileheader);
|
|
ASSERT(fhp->fh_metadata.md_allocents <= ALLOCMAP_SIZE);
|
|
/*
|
|
* If the caching state has changed, we do not update the allocation
|
|
* map. Return an error so that the caller will invalidate any
|
|
* data which might have been written.
|
|
*/
|
|
if (!C_CACHING(cp)) {
|
|
return(EFBIG);
|
|
}
|
|
/*
|
|
* We try to see if we can coalesce the current block into an existing
|
|
* allocation and mark it as such.
|
|
* If we can't do that then we make a new entry in the allocmap.
|
|
* when we run out of allocmaps, put the cnode in NOCACHE mode.
|
|
*/
|
|
for (ent = 0; ent < fhp->fh_metadata.md_allocents; ent++) {
|
|
|
|
allocp = &fhp->fh_allocmap[ent];
|
|
ASSERT(allocp <= &fhp->fh_allocmap[ALLOCMAP_SIZE - 1]);
|
|
/*
|
|
* There are seven different overlap or containment cases (ignoring
|
|
* the containment variations) for a new entry relative to the
|
|
* current entry under examination:
|
|
*
|
|
* 1) new entry will preceed
|
|
* 2) new entry is adjacent at the front
|
|
* 3) new entry overlaps the front
|
|
* 4) new entry contains
|
|
* 5) new entry is contained or is equivalent
|
|
* 6) new entry overlaps the end
|
|
* 7) new entry is adjacent at the end
|
|
*/
|
|
switch (cachefs_allocmap_region(allocp, off, off + size - 1)) {
|
|
case S_BEFORE|E_BEFORE:
|
|
/*
|
|
* The new entry preceeds and does not overlap the current
|
|
* entry being examined. A new entry is needed. Put it at
|
|
* the end of the map and sort the map.
|
|
*/
|
|
if (fhp->fh_metadata.md_allocents < ALLOCMAP_SIZE) {
|
|
/*
|
|
* Make room for the new entry by moving all entries
|
|
* up one slot. Start at the end of the map.
|
|
*/
|
|
for (i = fhp->fh_metadata.md_allocents; i > ent; i--) {
|
|
fhp->fh_allocmap[i] = fhp->fh_allocmap[i - 1];
|
|
}
|
|
allocp->am_start_off = off;
|
|
allocp->am_size = size;
|
|
CNODE_TRACE(CNTRACE_ALLOCMAP, cp, cachefs_update_allocmap,
|
|
allocp - fhp->fh_allocmap, allocp->am_size);
|
|
fhp->fh_metadata.md_allocents++;
|
|
CNODE_TRACE(CNTRACE_ALLOCENTS, cp, cachefs_update_allocmap,
|
|
fhp->fh_metadata.md_allocents, 0);
|
|
cp->c_flags |= CN_UPDATED;
|
|
CNODE_TRACE(CNTRACE_UPDATE, cp,
|
|
(void *)cachefs_update_allocmap, cp->c_flags,
|
|
fhp->fh_metadata.md_allocents);
|
|
ASSERT(fhp->fh_metadata.md_allocents <= ALLOCMAP_SIZE);
|
|
ASSERT(valid_file_header(fhp, NULL));
|
|
ASSERT(valid_allocmap(cp));
|
|
ASSERT(cachefs_zone_validate((caddr_t)fhp,
|
|
FILEHEADER_BLOCK_SIZE(C_TO_FSCACHE(cp)->fs_cache)));
|
|
error = 0;
|
|
} else {
|
|
/*
|
|
* The allocation map is full.
|
|
*/
|
|
error = EFBIG;
|
|
}
|
|
return(error);
|
|
case S_BEFORE|E_START:
|
|
/*
|
|
* front adjacent
|
|
* The new entry starts before and is adjacent to an
|
|
* existing entry. Adjust the existing one and coalesce.
|
|
*/
|
|
allocp->am_start_off = off;
|
|
allocp->am_size += size;
|
|
coalesce_allocmap(cp, ent);
|
|
return(0);
|
|
case S_START|E_AFTER:
|
|
case S_BEFORE|E_END:
|
|
case S_BEFORE|E_AFTER:
|
|
/*
|
|
* New entry will contain existing one.
|
|
*/
|
|
allocp->am_start_off = off;
|
|
allocp->am_size = size;
|
|
coalesce_allocmap(cp, ent);
|
|
return(0);
|
|
case S_START|E_END:
|
|
/*
|
|
* identical
|
|
*/
|
|
case S_START|E_MIDDLE:
|
|
case S_MIDDLE|E_MIDDLE:
|
|
case S_MIDDLE|E_END:
|
|
/*
|
|
* new contained
|
|
*/
|
|
return(0);
|
|
case S_BEFORE|E_MIDDLE:
|
|
/*
|
|
* Front overlap case.
|
|
*/
|
|
endoff = allocp->am_start_off + allocp->am_size;
|
|
allocp->am_start_off = off;
|
|
allocp->am_size = endoff - off;
|
|
coalesce_allocmap(cp, ent);
|
|
return(0);
|
|
case S_MIDDLE|E_AFTER:
|
|
/*
|
|
* End overlap case.
|
|
*/
|
|
allocp->am_size = endoff - allocp->am_start_off;
|
|
coalesce_allocmap(cp, ent);
|
|
return(0);
|
|
case S_END|E_AFTER:
|
|
/*
|
|
* End adjacent case.
|
|
*/
|
|
allocp->am_size += size;
|
|
coalesce_allocmap(cp, ent);
|
|
return(0);
|
|
case S_AFTER|E_AFTER:
|
|
/*
|
|
* follows, so loop continues
|
|
*/
|
|
break;
|
|
default:
|
|
/*
|
|
* invalid cases
|
|
*/
|
|
ASSERT(0);
|
|
}
|
|
}
|
|
/*
|
|
* If we get here, a new entry must be made at the end of the list.
|
|
* There were either no entries in the list or the new entry could
|
|
* not be coalesced with the last entry in the list.
|
|
* Check for room in the map first. Return EFBIG if the map is full.
|
|
*/
|
|
if (fhp->fh_metadata.md_allocents < ALLOCMAP_SIZE) {
|
|
/*
|
|
* Add a new entry at the end of the list and update the entry
|
|
* count.
|
|
*/
|
|
fhp->fh_allocmap[fhp->fh_metadata.md_allocents].am_size = size;
|
|
fhp->fh_allocmap[fhp->fh_metadata.md_allocents].am_start_off = off;
|
|
fhp->fh_metadata.md_allocents++;
|
|
} else {
|
|
error = EFBIG;
|
|
}
|
|
return(error);
|
|
}
|
|
|
|
void
|
|
cachefs_iodone(struct buf *bp)
|
|
{
|
|
ASSERT((bp->b_flags & (B_ASYNC | B_BDFLUSH)) == (B_ASYNC | B_BDFLUSH));
|
|
ASSERT(!(bp->b_flags & B_ERROR));
|
|
bp->b_flags |= (B_DONE | B_DELWRI);
|
|
bp->b_iodone = NULL;
|
|
brelse(bp);
|
|
}
|
|
|
|
int
|
|
cachefsio_write(struct cnode *cp, struct buf *bp, cred_t *cr)
|
|
{
|
|
int error;
|
|
enum backop_mode opmode;
|
|
int wflags;
|
|
int seg;
|
|
int err = 0;
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
off_t iooff;
|
|
u_int iolen;
|
|
char *addr = NULL;
|
|
uio_t uio;
|
|
iovec_t iov;
|
|
int ospl;
|
|
int mapped = BP_ISMAPPED(bp);
|
|
|
|
CFS_DEBUG(CFSDEBUG_VOPS | CFSDEBUG_RDWR,
|
|
printf( "cachefsio_write: ENTER cp 0x%p bp 0x%p cr 0x%p\n",
|
|
cp, bp, cr));
|
|
ASSERT(VALID_ADDR(cp));
|
|
ASSERT(VALID_ADDR(bp));
|
|
ASSERT(VALID_ADDR(cr));
|
|
ASSERT(VALID_ADDR(fscp));
|
|
ASSERT(!(FSC_TO_VFS(fscp)->vfs_flag & VFS_RDONLY));
|
|
ASSERT(WRITEALLOWED(fscp->fs_root->c_backvp, cr));
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_WRITE, (void *)cachefsio_write, current_pid(),
|
|
cp, cp->c_frontvp);
|
|
CACHEFS_STATS->cs_writes++;
|
|
if (cp->c_cred != NULL)
|
|
cr = cp->c_cred;
|
|
|
|
iooff = (off_t)BBTOOFF( bp->b_offset );
|
|
|
|
if (mapped) {
|
|
addr = bp->b_un.b_addr;
|
|
} else {
|
|
addr = bp_mapin(bp);
|
|
}
|
|
|
|
ASSERT((cp->c_size - iooff) > (off_t)0);
|
|
iolen = (u_int)MIN((off_t)bp->b_bcount, cp->c_size - iooff);
|
|
CNODE_TRACE(CNTRACE_WRITE, cp, (void *)cachefsio_write, iooff, iolen);
|
|
if ((bp->b_flags & (B_ASYNC | B_BDFLUSH)) == (B_ASYNC | B_BDFLUSH)) {
|
|
opmode = BACKOP_NONBLOCK;
|
|
} else {
|
|
opmode = BACKOP_BLOCK;
|
|
}
|
|
if (((C_ISFS_WRITE_AROUND(fscp) || C_ISFS_SINGLE(fscp)))) {
|
|
UIO_SETUP(&uio, &iov, addr, (int)iolen, iooff, UIO_NOSPACE,
|
|
FWRITE, (off_t)RLIM_INFINITY);
|
|
switch (BACK_WRITE_VP(cp, &uio, IO_SYNC | IO_DIRECT | IO_PFUSE_SAFE,
|
|
cr, opmode, &err)) {
|
|
case BACKOP_NETERR:
|
|
goto write_error;
|
|
case BACKOP_SUCCESS:
|
|
if (uio.uio_resid == 0) {
|
|
break;
|
|
}
|
|
err = EIO;
|
|
CFS_DEBUG(CFSDEBUG_DIO,
|
|
printf("cachefsio_write(line %d): incomplete back file "
|
|
"write: resid %d, addr 0x%p, size %d, offset "
|
|
"0x%llx\n", __LINE__, uio.uio_resid, addr,
|
|
(int)iolen, iooff));
|
|
case BACKOP_FAILURE:
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags |= CN_NEEDINVAL;
|
|
if ((err != EPERM) && (err != EACCES) && (err != EROFS)) {
|
|
cp->c_flags |= CN_NOCACHE;
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefsio_write,
|
|
(int)cp->c_flags, 0);
|
|
CACHEFS_STATS->cs_nocache++;
|
|
}
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_DIO | CFSDEBUG_ERROR,
|
|
printf("cachefsio_write(line %d): back file write "
|
|
"error %d, addr 0x%p, size %d, offset 0x%llx\n",
|
|
__LINE__, err, addr, (int)iolen, iooff));
|
|
goto write_error;
|
|
}
|
|
}
|
|
if (C_CACHING(cp)) {
|
|
/*
|
|
* First, check the allocation map to see if we have already read
|
|
* or written the blocks. If not, attempt to allocate the necessary
|
|
* blocks. Failing that, we nocache the file. The write will still
|
|
* go to the back file system.
|
|
*/
|
|
if ((cachefs_check_allocmap(cp, iooff, (off_t)iolen) == 0) &&
|
|
(err = cachefs_allocblocks(fscp->fs_cache, iolen, cr))) {
|
|
/*
|
|
* If we cannot allocate the necessary blocks, nocache the file
|
|
* and start over so it can be read from the back FS.
|
|
* Make sure the error is cleared so that we don't return
|
|
* a write error, as there was not really a write error.
|
|
*/
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags |= CN_NOCACHE;
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefsio_write,
|
|
(int)cp->c_flags, 0);
|
|
CACHEFS_STATS->cs_nocache++;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefsio_write(line %d): nocache cp 0x%p\n",
|
|
__LINE__, cp));
|
|
err = 0;
|
|
} else if (C_ISFS_SINGLE(fscp)) {
|
|
ASSERT(cp->c_frontfid.fid_len &&
|
|
(cp->c_frontfid.fid_len <= MAXFIDSZ));
|
|
if (front_dio) {
|
|
seg = UIO_NOSPACE;
|
|
wflags = IO_DIRECT | IO_TRUSTEDDIO;
|
|
} else {
|
|
seg = UIO_SYSSPACE;
|
|
wflags = 0;
|
|
}
|
|
UIO_SETUP(&uio, &iov, addr, ((int)iolen + BBMASK) & ~BBMASK,
|
|
iooff + DATA_START(fscp), seg, FWRITE, (off_t)RLIM_INFINITY);
|
|
err = FRONT_WRITE_VP(cp, &uio, wflags, sys_cred);
|
|
CNODE_TRACE(CNTRACE_DIOWRITE, cp, (void *)cachefsio_write,
|
|
(int)iooff + DATA_START(fscp), iolen);
|
|
if (err) {
|
|
CFS_DEBUG(CFSDEBUG_DIO,
|
|
printf("cachefsio_write(line %d): front file write error "
|
|
"%d, addr 0x%p, size %d, offset 0x%llx\n", __LINE__,
|
|
err, addr, (int)iolen, iooff));
|
|
if (err != EINTR) {
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags |= (CN_NOCACHE | CN_NEEDINVAL);
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefsio_write,
|
|
(int)cp->c_flags, 0);
|
|
CACHEFS_STATS->cs_nocache++;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefsio_write(line %d): nocache cp 0x%p\n",
|
|
__LINE__, cp));
|
|
}
|
|
err = 0;
|
|
} else if (uio.uio_resid) {
|
|
CFS_DEBUG(CFSDEBUG_DIO,
|
|
printf("cachefsio_write(line %d): incomplete front file "
|
|
"write: resid %d, addr 0x%p, size %d, offset "
|
|
"0x%llx\n", __LINE__, (int)uio.uio_resid, addr,
|
|
(int)iolen, iooff));
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags |= (CN_NOCACHE | CN_NEEDINVAL);
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefsio_write,
|
|
(int)cp->c_flags, 0);
|
|
CACHEFS_STATS->cs_nocache++;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefsio_write(line %d): nocache cp 0x%p\n",
|
|
__LINE__, cp));
|
|
err = 0;
|
|
} else {
|
|
/*
|
|
* Update the allocation map. If this fails, the file will
|
|
* be marked nocache.
|
|
*/
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
if (cachefs_update_allocmap(cp, iooff, (off_t)iolen)) {
|
|
cp->c_flags |= CN_NOCACHE;
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefsio_write,
|
|
(int)cp->c_flags, 0);
|
|
CACHEFS_STATS->cs_nocache++;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefsio_write(line %d): nocache cp 0x%p\n",
|
|
__LINE__, cp));
|
|
} else {
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
}
|
|
if ((cp->c_flags & CN_UPDATED) && !cp->c_frontvp) {
|
|
error = cachefs_getfrontvp(cp);
|
|
CFS_DEBUG(CFSDEBUG_ERROR, if (error)
|
|
printf("cachefsio_write(line %d): error %d "
|
|
"getting front vnode\n", __LINE__, error));
|
|
if (error) {
|
|
(void)cachefs_nocache(cp);
|
|
error = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
} else {
|
|
CACHEFS_STATS->cs_nocachewrites++;
|
|
}
|
|
write_error:
|
|
if (!mapped) {
|
|
bp_mapout( bp );
|
|
}
|
|
if (bp->b_error = err) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefsio_write: error %d\n", err));
|
|
if ((err == ETIMEDOUT) && (opmode == BACKOP_NONBLOCK)) {
|
|
err = bp->b_error = 0;
|
|
bp->b_iodone = cachefs_iodone;
|
|
} else {
|
|
bp->b_flags |= B_ERROR;
|
|
}
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_error = err;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
} else {
|
|
CFSOP_MODIFY_COBJECT(fscp, cp, cr);
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_error = err;
|
|
if (cp->c_written >= iolen) {
|
|
cp->c_written -= iolen;
|
|
} else {
|
|
cp->c_written = 0;
|
|
}
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
}
|
|
iodone( bp );
|
|
|
|
CFS_DEBUG(CFSDEBUG_VOPS | CFSDEBUG_RDWR,
|
|
printf("cachefsio_write: EXIT error %d\n", err));
|
|
CFS_DEBUG(CFSDEBUG_STALE,
|
|
if (err == ESTALE) printf("cachefsio_write: EXIT ESTALE\n"));
|
|
return (err);
|
|
}
|
|
|
|
int
|
|
cachefsio_read(struct cnode *cp, struct buf *bp, cred_t *cr)
|
|
{
|
|
int seg;
|
|
int rflags;
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
int error = 0;
|
|
off_t off = BBTOOFF( bp->b_offset );
|
|
char *addr = NULL;
|
|
ssize_t iolen = (bp->b_bcount + BBMASK) & ~BBMASK;
|
|
uio_t uio;
|
|
iovec_t iov;
|
|
int ospl;
|
|
int mapped = BP_ISMAPPED(bp);
|
|
#ifdef MH_R10000_SPECULATION_WAR
|
|
ssize_t real_iolen = 0;
|
|
char *real_addr = NULL;
|
|
#endif /* MH_R10000_SPECULATION_WAR */
|
|
|
|
CFS_DEBUG(CFSDEBUG_VOPS | CFSDEBUG_RDWR,
|
|
printf( "cachefsio_read: ENTER cp 0x%p bp 0x%p off %ld len %ld\n",
|
|
cp, bp, off, (long)iolen));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefsio_read, current_pid(),
|
|
cp, bp);
|
|
CNODE_TRACE(CNTRACE_READ, cp, (void *)cachefsio_read, bp->b_offset,
|
|
iolen);
|
|
|
|
ASSERT(VALID_ADDR(cp));
|
|
ASSERT(VALID_ADDR(bp));
|
|
ASSERT(VALID_ADDR(cr));
|
|
ASSERT(!C_CACHING(cp) ||
|
|
(cp->c_frontfid.fid_len && (cp->c_frontfid.fid_len <= MAXFIDSZ)));
|
|
|
|
CACHEFS_STATS->cs_reads++;
|
|
if (mapped) {
|
|
addr = bp->b_un.b_addr;
|
|
} else {
|
|
addr = bp_mapin( bp );
|
|
}
|
|
ospl = mutex_spinlock( &cp->c_statelock );
|
|
/*
|
|
* If we're not cacheing this file, just go directly to the back file.
|
|
*/
|
|
if (!C_CACHING(cp)) {
|
|
mutex_spinunlock( &cp->c_statelock, ospl );
|
|
CACHEFS_STATS->cs_nocachereads++;
|
|
UIO_SETUP( &uio, &iov, addr, iolen, off, UIO_NOSPACE, FREAD,
|
|
(off_t)RLIM_INFINITY);
|
|
(void)BACK_READ_VP( cp, &uio, IO_SYNC | IO_DIRECT | IO_PFUSE_SAFE, cp->c_cred,
|
|
(bp->b_flags & B_ASYNC) ? BACKOP_NONBLOCK : BACKOP_BLOCK, &error );
|
|
CFS_DEBUG(CFSDEBUG_DIO,
|
|
if ( error )
|
|
printf("cachefsio_read(line %d): back file read error %d, "
|
|
"addr 0x%p, size %d, offset 0x%llx\n", __LINE__, error,
|
|
addr, iolen, off));
|
|
} else if (cachefs_check_allocmap(cp, off, (off_t)iolen)) {
|
|
mutex_spinunlock( &cp->c_statelock, ospl );
|
|
CACHEFS_STATS->cs_readhit++;
|
|
/*
|
|
* File was populated so we get the page from the
|
|
* frontvp
|
|
* do this with direct I/O to bypass the buffer cache
|
|
*/
|
|
if (front_dio) {
|
|
seg = UIO_NOSPACE;
|
|
rflags = IO_SYNC | IO_DIRECT | IO_TRUSTEDDIO;
|
|
} else {
|
|
seg = UIO_SYSSPACE;
|
|
rflags = 0;
|
|
}
|
|
#ifdef MH_R10000_SPECULATION_WAR
|
|
/*
|
|
* we assume that the address will always be aligned and that
|
|
* the only thing which might not be correct is the length
|
|
*/
|
|
if (iolen % DIO_MINIO(fscp->fs_cache)) {
|
|
real_addr = addr;
|
|
real_iolen = iolen;
|
|
iolen = ((iolen + DIO_MINIO(fscp->fs_cache) - 1) /
|
|
DIO_MINIO(fscp->fs_cache)) * DIO_MINIO(fscp->fs_cache);
|
|
addr = kmem_zalloc(iolen, KM_SLEEP);
|
|
}
|
|
#endif /* MH_R10000_SPECULATION_WAR */
|
|
UIO_SETUP( &uio, &iov, addr, iolen, off + DATA_START(fscp),
|
|
seg, FREAD, (off_t)RLIM_INFINITY );
|
|
error = FRONT_READ_VP( cp, &uio, rflags, sys_cred );
|
|
#ifdef MH_R10000_SPECULATION_WAR
|
|
if (real_iolen) {
|
|
bcopy(addr, real_addr, real_iolen);
|
|
if (uio.uio_resid < (iolen - real_iolen)) {
|
|
uio.uio_resid = 0;
|
|
} else {
|
|
uio.uio_resid -= (iolen - real_iolen);
|
|
}
|
|
kmem_free(addr, iolen);
|
|
addr = real_addr;
|
|
iolen = real_iolen;
|
|
}
|
|
#endif /* MH_R10000_SPECULATION_WAR */
|
|
if (error) {
|
|
CFS_DEBUG(CFSDEBUG_DIO,
|
|
printf("cachefsio_read(line %d): front file read error "
|
|
"%d, addr 0x%p, size %d, offset 0x%llx\n", __LINE__,
|
|
error, addr, iolen, off));
|
|
if (error != EINTR) {
|
|
/*
|
|
* Mark the cnode CN_NOCACHE but do not call cachefs_nocache
|
|
* until later. It is very important that cachefs_nocache
|
|
* not be called until after brelse. A deadlock may result
|
|
* otherwise. This means that the calling of cachefs_nocache
|
|
* must be deferred until fscache_sync or cachefs_inactive.
|
|
*/
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags |= (CN_NOCACHE | CN_NEEDINVAL);
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefsio_read,
|
|
(int)cp->c_flags, 0);
|
|
CACHEFS_STATS->cs_nocache++;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefsio_read(line %d): nocache cp 0x%p\n",
|
|
__LINE__, cp));
|
|
UIO_SETUP( &uio, &iov, addr, iolen, off, UIO_NOSPACE,
|
|
FREAD, (off_t)RLIM_INFINITY);
|
|
(void)BACK_READ_VP( cp, &uio, IO_SYNC | IO_DIRECT | IO_PFUSE_SAFE,
|
|
cp->c_cred,
|
|
(bp->b_flags & B_ASYNC) ? BACKOP_NONBLOCK : BACKOP_BLOCK,
|
|
&error );
|
|
CFS_DEBUG(CFSDEBUG_DIO,
|
|
if ( error )
|
|
printf("cachefsio_read(line %d): back file read error "
|
|
"%d, addr 0x%p, size %d, offset 0x%llx\n",
|
|
__LINE__, error, addr, iolen, off));
|
|
}
|
|
}
|
|
} else {
|
|
mutex_spinunlock( &cp->c_statelock, ospl );
|
|
CACHEFS_STATS->cs_readmiss++;
|
|
/*
|
|
* We are assuming that this request does not overlap an existing
|
|
* set of blocks.
|
|
*/
|
|
if (error = cachefs_allocblocks(fscp->fs_cache, iolen, cr)) {
|
|
/*
|
|
* If we cannot allocate the necessary blocks, nocache the file
|
|
* and start over so it can be read from the back FS.
|
|
*/
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
cp->c_flags |= CN_NOCACHE;
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefsio_read,
|
|
(int)cp->c_flags, 0);
|
|
CACHEFS_STATS->cs_nocache++;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefsio_read(line %d): nocache cp 0x%p\n",
|
|
__LINE__, cp));
|
|
}
|
|
UIO_SETUP( &uio, &iov, addr, iolen, off, UIO_NOSPACE, FREAD,
|
|
(off_t)RLIM_INFINITY);
|
|
(void)BACK_READ_VP( cp, &uio, IO_SYNC | IO_DIRECT | IO_PFUSE_SAFE, cp->c_cred,
|
|
(bp->b_flags & B_ASYNC) ? BACKOP_NONBLOCK : BACKOP_BLOCK, &error );
|
|
if ( !error && C_CACHING(cp) ) {
|
|
/*
|
|
* Write the data to the front file. If there is no error,
|
|
* update the allocation map.
|
|
*/
|
|
if (front_dio) {
|
|
seg = UIO_NOSPACE;
|
|
rflags = IO_SYNC | IO_DIRECT | IO_TRUSTEDDIO;
|
|
} else {
|
|
seg = UIO_SYSSPACE;
|
|
rflags = 0;
|
|
}
|
|
UIO_SETUP( &uio, &iov, addr, iolen,
|
|
off + DATA_START(fscp), seg, FWRITE,
|
|
(off_t)RLIM_INFINITY);
|
|
error = FRONT_WRITE_VP( cp, &uio,
|
|
rflags, sys_cred );
|
|
CNODE_TRACE(CNTRACE_DIOWRITE, cp, (void *)cachefsio_read,
|
|
(int)off + DATA_START(fscp), iolen);
|
|
if (!error && uio.uio_resid) {
|
|
error = EIO;
|
|
} else if (!error) {
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
if (cachefs_update_allocmap(cp, off, (off_t)iolen)) {
|
|
cp->c_flags |= CN_NOCACHE;
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefsio_read,
|
|
(int)cp->c_flags, 0);
|
|
CACHEFS_STATS->cs_nocache++;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefsio_read(line %d): nocache cp 0x%p\n",
|
|
__LINE__, cp));
|
|
} else {
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
}
|
|
if ((cp->c_flags & CN_UPDATED) && !cp->c_frontvp) {
|
|
error = cachefs_getfrontvp(cp);
|
|
CFS_DEBUG(CFSDEBUG_ERROR, if (error)
|
|
printf("cachefsio_read(line %d): error %d "
|
|
"getting front vnode\n", __LINE__, error));
|
|
if (error) {
|
|
(void)cachefs_nocache(cp);
|
|
error = 0;
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
} else {
|
|
CFS_DEBUG(CFSDEBUG_DIO,
|
|
printf("cachefsio_read(line %d): front file write error "
|
|
"%d, addr 0x%p, size %d, offset 0x%llx\n", __LINE__,
|
|
error, addr, iolen, off));
|
|
#endif /* DEBUG */
|
|
}
|
|
#ifdef DEBUG
|
|
} else if (error != EINTR) {
|
|
CFS_DEBUG(CFSDEBUG_DIO,
|
|
printf("cachefsio_read(line %d): back file read error %d, "
|
|
"addr 0x%p, size %d, offset 0x%llx\n", __LINE__, error,
|
|
addr, iolen, off));
|
|
#endif /* DEBUG */
|
|
}
|
|
}
|
|
if (!mapped) {
|
|
bp_mapout( bp );
|
|
}
|
|
bp->b_resid = uio.uio_resid;
|
|
if ( bp->b_error = error ) {
|
|
bp->b_flags |= B_ERROR;
|
|
CACHEFS_STATS->cs_readerrors++;
|
|
}
|
|
iodone( bp );
|
|
CFS_DEBUG(CFSDEBUG_VOPS | CFSDEBUG_RDWR,
|
|
printf("cachefsio_read: EXIT error %d\n", error));
|
|
CFS_DEBUG(CFSDEBUG_STALE,
|
|
if (error == ESTALE) printf("cachefsio_read: EXIT ESTALE\n"));
|
|
return(error);
|
|
}
|
|
|
|
void
|
|
cachefs_workq_init(struct cachefs_workq *qp)
|
|
{
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_workq_init,
|
|
current_pid(), qp, 0);
|
|
ASSERT(VALID_ADDR(qp));
|
|
qp->wq_head = qp->wq_tail = NULL;
|
|
qp->wq_length =
|
|
qp->wq_thread_count =
|
|
qp->wq_max_len =
|
|
qp->wq_halt_request = 0;
|
|
qp->wq_keepone = 0;
|
|
sv_init(&qp->wq_req_sv, SV_DEFAULT, "cachefs async io cv");
|
|
sv_init(&qp->wq_halt_sv, SV_DEFAULT, "cachefs halt drain cv");
|
|
spinlock_init(&qp->wq_queue_lock, "work queue lock");
|
|
}
|
|
|
|
void
|
|
cachefs_workq_destroy( struct cachefs_workq *qp )
|
|
{
|
|
int printed = 0;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_workq_destroy,
|
|
current_pid(), qp, 0);
|
|
ASSERT(VALID_ADDR(qp));
|
|
while ( cachefs_async_halt(qp) == EBUSY ) {
|
|
if ( !printed ) {
|
|
printed = 1;
|
|
cmn_err( CE_WARN,
|
|
"cachefs_workq_destroy: waiting for async daemons\n" );
|
|
}
|
|
}
|
|
if ( printed ) {
|
|
cmn_err( CE_WARN,
|
|
"cachefs_workq_destroy: async daemons halted\n" );
|
|
}
|
|
sv_destroy( &qp->wq_req_sv );
|
|
sv_destroy( &qp->wq_halt_sv );
|
|
spinlock_destroy( &qp->wq_queue_lock );
|
|
}
|
|
|
|
int
|
|
cachefs_async_start(cachefscache_t *cachep)
|
|
{
|
|
rval_t rval;
|
|
int error = 0;
|
|
int ospl;
|
|
|
|
ospl = mutex_spinlock(&cachep->c_workq.wq_queue_lock);
|
|
if (cachep->c_workq.wq_thread_count == 0) {
|
|
cachep->c_workq.wq_keepone = 1;
|
|
cachep->c_workq.wq_thread_busy = 0;
|
|
cachep->c_workq.wq_halt_request = 0;
|
|
mutex_spinunlock(&cachep->c_workq.wq_queue_lock, ospl);
|
|
/*
|
|
* create the cachefs_async_daemon process
|
|
*/
|
|
if ( (error = fork(NULL, &rval)) == 0 ) {
|
|
if ( rval.r_val1 == 0 ) {
|
|
/*
|
|
* this is the new process
|
|
* call cachefs_async_daemon which never returns
|
|
*/
|
|
cachefs_async_daemon( &cachep->c_workq );
|
|
/* NOTREACHED */
|
|
}
|
|
ospl = mutex_spinlock(&cachep->c_workq.wq_queue_lock);
|
|
cachep->c_workq.wq_thread_count++;
|
|
mutex_spinunlock(&cachep->c_workq.wq_queue_lock, ospl);
|
|
} else {
|
|
cmn_err(CE_WARN,
|
|
"CacheFS: unable to start async daemon, error %d\n",
|
|
error);
|
|
}
|
|
} else {
|
|
mutex_spinunlock(&cachep->c_workq.wq_queue_lock, ospl);
|
|
}
|
|
return(error);
|
|
}
|
|
|
|
void
|
|
cachefs_async_daemon(struct cachefs_workq *qp)
|
|
{
|
|
rval_t rval;
|
|
struct cachefs_req *rp;
|
|
int left;
|
|
k_sigset_t catch_sigs = {
|
|
sigmask(SIGINT) | sigmask(SIGQUIT) | sigmask(SIGKILL) | sigmask(SIGTERM)
|
|
};
|
|
int old_spl;
|
|
int error = 0;
|
|
rval_t rvp;
|
|
uthread_t *ut;
|
|
sigvec_t *sigvp;
|
|
proc_t *curp = NULL;
|
|
timespec_t ts;
|
|
timespec_t remaining;
|
|
|
|
CFS_DEBUG(CFSDEBUG_PROCS,
|
|
printf( "cachefs_async_daemon: ENTER qp 0x%p\n", qp ));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_async_daemon,
|
|
current_pid(), qp, 0);
|
|
ASSERT(VALID_ADDR(qp));
|
|
/*
|
|
* No need for user VM.
|
|
*/
|
|
vrelvm();
|
|
|
|
/*
|
|
* close all open files
|
|
*/
|
|
fdt_release();
|
|
|
|
proc_start: /* where duplicate processes must start */
|
|
ut = curuthread;
|
|
curp = UT_TO_PROC(ut);
|
|
|
|
/*
|
|
* Set scheduler policy and priority
|
|
*/
|
|
VPROC_SCHED_SETSCHEDULER(curvprocp, 21, SCHED_TS, &rvp.r_val1, &error);
|
|
if (error) {
|
|
printf("Unable to set cachefsd scheduler priority/policy: error=%d\n",
|
|
error);
|
|
}
|
|
|
|
/*
|
|
* set things up so zombies don't lie about
|
|
*/
|
|
VPROC_GET_SIGVEC(curvprocp, VSIGVEC_UPDATE, sigvp);
|
|
sigvp->sv_flags |= SNOWAIT|SNOCLDSTOP;
|
|
|
|
/*
|
|
* set up the signals to be caught
|
|
*/
|
|
sigfillset(&sigvp->sv_sigign);
|
|
sigdiffset(&sigvp->sv_sigign, &catch_sigs);
|
|
sigvp->sv_sigcatch = catch_sigs;
|
|
VPROC_PUT_SIGVEC(curvprocp);
|
|
|
|
old_spl = ut_lock(ut);
|
|
sigemptyset(ut->ut_sighold);
|
|
ut_unlock(ut, old_spl);
|
|
|
|
bcopy("cachefs_async", (void *)curp->p_psargs, 14);
|
|
bcopy("cachefs_async", (void *)curp->p_comm, 13);
|
|
CFS_DEBUG(CFSDEBUG_PROCS,
|
|
printf( "cachefs_async_daemon: proc_start %s pid %d\n",
|
|
curp->p_psargs, (int)curp->p_pid ));
|
|
|
|
ASSERT(!issplhi(getsr()));
|
|
old_spl = mutex_spinlock(&qp->wq_queue_lock);
|
|
left = 1;
|
|
for (;;) {
|
|
/* if there are no pending requests */
|
|
if (qp->wq_head == NULL) {
|
|
/* see if thread should exit */
|
|
if (qp->wq_halt_request || (left == 0)) {
|
|
if (((qp->wq_thread_count - qp->wq_thread_busy)
|
|
> 1) ||
|
|
(qp->wq_keepone == 0))
|
|
break;
|
|
}
|
|
|
|
/* wake up thread in async_halt if necessary */
|
|
if (qp->wq_halt_request)
|
|
{
|
|
CFS_DEBUG(CFSDEBUG_PROCS,
|
|
printf(
|
|
"cachefs_async_daemon[%d]: Halt request, %s\n",
|
|
(int)curp->p_pid,
|
|
qp->wq_keepone ? "keep one" : "keep none" ));
|
|
sv_signal(&qp->wq_halt_sv);
|
|
if ((qp->wq_thread_count > 1) || (qp->wq_keepone == 0)) {
|
|
break;
|
|
}
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_PROCS,
|
|
printf( "cachefs_async_daemon[%d]: waiting for requests\n",
|
|
(int)curp->p_pid ));
|
|
|
|
/* sleep until there is something to do */
|
|
ts.tv_sec = CFS_ASYNC_TIMEOUT;
|
|
ts.tv_nsec = 0;
|
|
(void)sv_timedwait( &qp->wq_req_sv, PZERO | PLTWAIT,
|
|
&qp->wq_queue_lock, old_spl, 0,
|
|
&ts, &remaining);
|
|
ASSERT(!issplhi(getsr()));
|
|
left = remaining.tv_sec || remaining.tv_nsec;
|
|
old_spl = mutex_spinlock( &qp->wq_queue_lock );
|
|
CFS_DEBUG(CFSDEBUG_PROCS,
|
|
printf("cachefs_async_daemon[%d]: %s, left = %d\n",
|
|
(int)curp->p_pid, left ? "request" : "cv timeout",
|
|
left ));
|
|
continue;
|
|
/*
|
|
* see if we need another process
|
|
* simply duplicate the current process
|
|
*/
|
|
} else if ((qp->wq_thread_count < cachefs_max_threads) &&
|
|
((qp->wq_length >= (qp->wq_thread_count * 2)) ||
|
|
((qp->wq_thread_count - qp->wq_thread_busy) == 1))) {
|
|
qp->wq_thread_count++;
|
|
qp->wq_thread_busy++;
|
|
mutex_spinunlock(&qp->wq_queue_lock, old_spl);
|
|
if ( fork(NULL, &rval) == 0 ) {
|
|
if ( rval.r_val1 == 0 ) {
|
|
/*
|
|
* this is the new process
|
|
* it has to go to the beginning and acquire
|
|
* the mutex before doing anything else
|
|
*/
|
|
goto proc_start;
|
|
} else {
|
|
/*
|
|
* this case is the parent process, simply continue
|
|
* normal execution
|
|
*/
|
|
old_spl = mutex_spinlock(&qp->wq_queue_lock);
|
|
}
|
|
} else {
|
|
/*
|
|
* If the for failed, decrement the thread count.
|
|
*/
|
|
old_spl = mutex_spinlock(&qp->wq_queue_lock);
|
|
qp->wq_thread_count--;
|
|
}
|
|
} else {
|
|
qp->wq_thread_busy++;
|
|
}
|
|
if (qp->wq_head != NULL) {
|
|
left = 1;
|
|
|
|
/* remove request from the list */
|
|
rp = qp->wq_head;
|
|
qp->wq_head = rp->cfs_next;
|
|
if (rp->cfs_next == NULL)
|
|
qp->wq_tail = NULL;
|
|
|
|
/* decrement count of requests */
|
|
qp->wq_length--;
|
|
|
|
/* do the request */
|
|
mutex_spinunlock(&qp->wq_queue_lock, old_spl);
|
|
cachefs_do_req(rp);
|
|
ASSERT(!issplhi(getsr()));
|
|
old_spl = mutex_spinlock(&qp->wq_queue_lock);
|
|
}
|
|
qp->wq_thread_busy--;
|
|
}
|
|
ASSERT(qp->wq_head == NULL);
|
|
qp->wq_thread_count--;
|
|
ASSERT((qp->wq_thread_count > 0) || !qp->wq_keepone);
|
|
if (qp->wq_halt_request && qp->wq_thread_count == 0)
|
|
sv_signal(&qp->wq_halt_sv);
|
|
mutex_spinunlock(&qp->wq_queue_lock, old_spl);
|
|
CFS_DEBUG(CFSDEBUG_PROCS,
|
|
printf( "cachefs_async_daemon[%d]: EXIT (process exiting, queue 0x%p, "
|
|
"thread count now %d)\n", (int)curp->p_pid, qp,
|
|
qp->wq_thread_count));
|
|
exit(CLD_EXITED, 0);
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
/*
|
|
* attempt to halt all the async threads associated with a given workq
|
|
*/
|
|
int
|
|
cachefs_async_halt(struct cachefs_workq *qp)
|
|
{
|
|
int error = 0;
|
|
int ospl;
|
|
timespec_t ts;
|
|
|
|
CFS_DEBUG(CFSDEBUG_PROCS,
|
|
printf( "cachefs_async_halt: ENTER qp 0x%p\n", qp));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_async_halt,
|
|
current_pid(), qp, 1);
|
|
ASSERT(VALID_ADDR(qp));
|
|
ospl = mutex_spinlock(&qp->wq_queue_lock);
|
|
qp->wq_keepone = 0;
|
|
|
|
if (qp->wq_thread_count > 0) {
|
|
qp->wq_halt_request = 1;
|
|
sv_broadcast(&qp->wq_req_sv);
|
|
ts.tv_sec = CFS_ASYNC_TIMEOUT;
|
|
ts.tv_nsec = 0;
|
|
(void)sv_timedwait( &qp->wq_halt_sv, PZERO,
|
|
&qp->wq_queue_lock, ospl, 0, &ts, NULL);
|
|
ASSERT(!issplhi(getsr()));
|
|
ospl = mutex_spinlock( &qp->wq_queue_lock);
|
|
qp->wq_halt_request = 0;
|
|
qp->wq_keepone = 1;
|
|
if (qp->wq_thread_count > 0) {
|
|
error = EBUSY;
|
|
} else {
|
|
ASSERT(qp->wq_length == 0 && qp->wq_head == NULL);
|
|
}
|
|
}
|
|
mutex_spinunlock(&qp->wq_queue_lock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_PROCS,
|
|
printf( "cachefs_async_halt: EXIT error = %d\n", error ));
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
cachefs_addqueue(struct cachefs_req *rp, struct cachefs_workq *qp)
|
|
{
|
|
int error = 0;
|
|
int ospl;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_addqueue, current_pid(),
|
|
rp, qp);
|
|
ASSERT(VALID_ADDR(rp));
|
|
ASSERT(VALID_ADDR(qp));
|
|
ASSERT(!issplhi(getsr()));
|
|
ospl = mutex_spinlock(&qp->wq_queue_lock);
|
|
if (qp->wq_thread_count == 0) {
|
|
error = EBUSY;
|
|
cmn_err( CE_WARN, "cachefs_addqueue: no async procs for queue 0x%p\n",
|
|
qp );
|
|
goto out;
|
|
}
|
|
CACHEFS_STATS->cs_asyncreqs++;
|
|
if (qp->wq_tail)
|
|
qp->wq_tail->cfs_next = rp;
|
|
else
|
|
qp->wq_head = rp;
|
|
qp->wq_tail = rp;
|
|
rp->cfs_next = NULL;
|
|
qp->wq_length++;
|
|
if (qp->wq_length > qp->wq_max_len)
|
|
qp->wq_max_len = qp->wq_length;
|
|
|
|
sv_signal(&qp->wq_req_sv);
|
|
out:
|
|
mutex_spinunlock(&qp->wq_queue_lock, ospl);
|
|
ASSERT(!issplhi(getsr()));
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
cachefs_async_read(struct cachefs_io_req *prp, cred_t *cr)
|
|
{
|
|
struct cnode *cp = prp->cp_cp;
|
|
struct buf *bp = prp->cp_buf;
|
|
int ospl;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_async_read,
|
|
current_pid(), prp, cr);
|
|
ASSERT(VALID_ADDR(prp));
|
|
ASSERT(VALID_ADDR(cr));
|
|
ASSERT(cp->c_nio > 0);
|
|
ASSERT(cp->c_ioflags & CIO_ASYNCREAD);
|
|
(void)cachefsio_read( cp, bp, cr );
|
|
ospl = mutex_spinlock(&cp->c_iolock);
|
|
if (--cp->c_nio == 0) {
|
|
cp->c_ioflags &= ~(CIO_ASYNCWRITE | CIO_ASYNCREAD);
|
|
sv_broadcast(&cp->c_iosv);
|
|
}
|
|
mutex_spinunlock(&cp->c_iolock, ospl);
|
|
}
|
|
|
|
static void
|
|
cachefs_async_write(struct cachefs_io_req *prp, cred_t *cr)
|
|
{
|
|
struct cnode *cp = prp->cp_cp;
|
|
struct buf *bp = prp->cp_buf;
|
|
int ospl;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_async_write,
|
|
current_pid(), prp, cr);
|
|
ASSERT(VALID_ADDR(prp));
|
|
ASSERT(VALID_ADDR(cr));
|
|
ASSERT(cp->c_nio > 0);
|
|
ASSERT(cp->c_ioflags & CIO_ASYNCWRITE);
|
|
(void)cachefsio_write( cp, bp, cr );
|
|
ospl = mutex_spinlock(&cp->c_iolock);
|
|
if (--cp->c_nio == 0) {
|
|
cp->c_ioflags &= ~(CIO_ASYNCWRITE | CIO_ASYNCREAD);
|
|
sv_broadcast(&cp->c_iosv);
|
|
}
|
|
mutex_spinunlock(&cp->c_iolock, ospl);
|
|
}
|
|
|
|
void
|
|
cachefs_do_req(struct cachefs_req *rp)
|
|
{
|
|
int ospl;
|
|
cnode_t *dcp;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_do_req, current_pid(),
|
|
rp, rp->cfs_cmd);
|
|
ASSERT(VALID_ADDR(rp));
|
|
ASSERT(!issplhi(getsr()));
|
|
switch (rp->cfs_cmd) {
|
|
case CFS_POPULATE_DIR: /* populate a file */
|
|
CFS_DEBUG(CFSDEBUG_ASYNC,
|
|
printf( "cachefs_do_req: CFS_POPULATE_DIR\n" ));
|
|
dcp = rp->cfs_req_u.cu_popdir.cpd_cp;
|
|
rw_enter(&dcp->c_rwlock, RW_WRITER);
|
|
cachefs_populate_dir(dcp, rp->cfs_cr);
|
|
ASSERT(!issplhi(getsr()));
|
|
rw_exit(&dcp->c_rwlock);
|
|
VN_RELE(CTOV(dcp));
|
|
CNODE_TRACE(CNTRACE_HOLD, dcp, (void *)cachefs_do_req,
|
|
CTOV(dcp)->v_count, 0);
|
|
break;
|
|
case CFS_CACHE_SYNC:
|
|
CFS_DEBUG(CFSDEBUG_ASYNC,
|
|
printf( "cachefs_do_req: CFS_CACHE_SYNC\n" ));
|
|
cachefs_cache_sync(rp->cfs_req_u.cu_fs_sync.cf_cachep);
|
|
ospl = mutex_spinlock(
|
|
&rp->cfs_req_u.cu_fs_sync.cf_cachep->c_contentslock);
|
|
rp->cfs_req_u.cu_fs_sync.cf_cachep->c_syncs--;
|
|
mutex_spinunlock(
|
|
&rp->cfs_req_u.cu_fs_sync.cf_cachep->c_contentslock, ospl);
|
|
ASSERT(!issplhi(getsr()));
|
|
break;
|
|
case CFS_ASYNCREAD:
|
|
CFS_DEBUG(CFSDEBUG_ASYNC,
|
|
printf( "cachefs_do_req: CFS_ASYNCREAD\n" ));
|
|
cachefs_async_read(&rp->cfs_req_u.cu_io, rp->cfs_cr);
|
|
ASSERT(!issplhi(getsr()));
|
|
break;
|
|
case CFS_ASYNCWRITE:
|
|
CFS_DEBUG(CFSDEBUG_ASYNC,
|
|
printf( "cachefs_do_req: CFS_ASYNCWRITE\n" ));
|
|
cachefs_async_write(&rp->cfs_req_u.cu_io, rp->cfs_cr);
|
|
ASSERT(!issplhi(getsr()));
|
|
break;
|
|
case CFS_NOOP:
|
|
CFS_DEBUG(CFSDEBUG_ASYNC,
|
|
printf( "cachefs_do_req: CFS_NOOP\n" ));
|
|
break;
|
|
default:
|
|
panic("c_do_req: Invalid CFS async operation\n");
|
|
}
|
|
crfree(rp->cfs_cr);
|
|
CACHEFS_ZONE_FREE(Cachefs_req_zone, rp);
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
int cachefs_mem_usage = 0;
|
|
int cachefs_zone_usage = 0;
|
|
struct km_wrap *Cachefs_memlist = NULL;
|
|
|
|
lock_t cachefs_kmem_lock;
|
|
|
|
void *
|
|
cachefs_kmem_alloc(void *(*func)(size_t, int), int size, int flag)
|
|
{
|
|
caddr_t mp = NULL;
|
|
struct km_wrap *kwp;
|
|
int n = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
|
|
int allocsize = n + (2 * WRAPSIZE);
|
|
int old_spl;
|
|
|
|
ASSERT(size != 0);
|
|
ASSERT(n >= size);
|
|
ASSERT(n < (size + sizeof(void *)));
|
|
mp = (func)(allocsize, flag);
|
|
if (mp == NULL) {
|
|
return (NULL);
|
|
}
|
|
old_spl = mutex_spinlock( &cachefs_kmem_lock );
|
|
/*LINTED alignment okay*/
|
|
kwp = (struct km_wrap *)mp;
|
|
kwp->kw_size = allocsize;
|
|
kwp->kw_req = size;
|
|
kwp->kw_caller = (void *)__return_address;
|
|
kwp->kw_prev = NULL;
|
|
kwp->kw_next = Cachefs_memlist;
|
|
if (Cachefs_memlist) {
|
|
Cachefs_memlist->kw_prev = kwp;
|
|
}
|
|
Cachefs_memlist = kwp;
|
|
/*LINTED alignment okay*/
|
|
kwp->kw_other = (struct km_wrap *) ((__psunsigned_t)mp + WRAPSIZE + n);
|
|
kwp = (struct km_wrap *)kwp->kw_other;
|
|
ASSERT(!((__psunsigned_t)kwp & (sizeof(void *) - 1)));
|
|
ASSERT((int)(((__psunsigned_t)kwp + (__psunsigned_t)WRAPSIZE) -
|
|
(__psunsigned_t)mp) == allocsize);
|
|
kwp->kw_size = allocsize;
|
|
kwp->kw_req = size;
|
|
/*LINTED alignment okay*/
|
|
kwp->kw_other = (struct km_wrap *)mp;
|
|
kwp->kw_caller = kwp->kw_prev = kwp->kw_next = NULL;
|
|
|
|
ASSERT(cachefs_mem_usage >= 0);
|
|
cachefs_mem_usage += allocsize;
|
|
mutex_spinunlock( &cachefs_kmem_lock, old_spl );
|
|
|
|
ASSERT(VALID_ADDR((__psunsigned_t)mp + (__psunsigned_t)WRAPSIZE));
|
|
ASSERT(!(((__psunsigned_t)mp + (__psunsigned_t)WRAPSIZE) &
|
|
(__psunsigned_t)(sizeof(void *) - 1)));
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_kmem_alloc,
|
|
mp, allocsize, __return_address);
|
|
|
|
return (mp + (__psunsigned_t)WRAPSIZE);
|
|
}
|
|
|
|
int
|
|
cachefs_kmem_validate(caddr_t mp, int size)
|
|
{
|
|
struct km_wrap *front_kwp;
|
|
struct km_wrap *back_kwp;
|
|
int n = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
|
|
int freesize = n + (2 * WRAPSIZE);
|
|
|
|
ASSERT(n < (size + sizeof(void *)));
|
|
ASSERT(VALID_ADDR(mp));
|
|
ASSERT(VALID_ADDR((u_long)mp + (u_long)n));
|
|
/*LINTED alignment okay*/
|
|
front_kwp = (struct km_wrap *)(mp - (__psunsigned_t)WRAPSIZE);
|
|
/*LINTED alignment okay*/
|
|
back_kwp = (struct km_wrap *)((caddr_t)mp + n);
|
|
ASSERT(VALID_ADDR(front_kwp));
|
|
ASSERT(VALID_ADDR(back_kwp));
|
|
|
|
return(!((__psunsigned_t)front_kwp & (sizeof(void *) - 1)) &&
|
|
!((__psunsigned_t)back_kwp & (sizeof(void *) - 1)) &&
|
|
(front_kwp->kw_req == size) &&
|
|
(front_kwp->kw_other == back_kwp) &&
|
|
(front_kwp->kw_size == freesize) &&
|
|
(back_kwp->kw_req == size) &&
|
|
(back_kwp->kw_other == front_kwp) &&
|
|
(back_kwp->kw_size == freesize) &&
|
|
(back_kwp->kw_caller == NULL) &&
|
|
(back_kwp->kw_prev == NULL) &&
|
|
(back_kwp->kw_next == NULL));
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
int
|
|
cachefs_zone_validate(caddr_t mp, int size)
|
|
{
|
|
#ifdef CFS_DEBUG_ZONES
|
|
return (cachefs_kmem_validate(mp, size));
|
|
#else
|
|
return(1);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
cachefs_kmem_free(caddr_t mp, int size)
|
|
{
|
|
struct km_wrap *front_kwp;
|
|
struct km_wrap *back_kwp;
|
|
int n = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
|
|
int freesize = n + (2 * WRAPSIZE);
|
|
caddr_t p;
|
|
int old_spl;
|
|
|
|
ASSERT(cachefs_kmem_validate(mp, size));
|
|
|
|
/*LINTED alignment okay*/
|
|
front_kwp = (struct km_wrap *)(mp - (__psunsigned_t)WRAPSIZE);
|
|
/*LINTED alignment okay*/
|
|
back_kwp = (struct km_wrap *)((caddr_t)mp + n);
|
|
|
|
old_spl = mutex_spinlock( &cachefs_kmem_lock );
|
|
if (front_kwp->kw_next) {
|
|
front_kwp->kw_next->kw_prev = front_kwp->kw_prev;
|
|
}
|
|
if (front_kwp->kw_prev) {
|
|
front_kwp->kw_prev->kw_next = front_kwp->kw_next;
|
|
} else {
|
|
Cachefs_memlist = front_kwp->kw_next;
|
|
}
|
|
cachefs_mem_usage -= freesize;
|
|
ASSERT(cachefs_mem_usage >= 0);
|
|
ASSERT(!cachefs_mem_usage || Cachefs_memlist);
|
|
mutex_spinunlock( &cachefs_kmem_lock, old_spl );
|
|
|
|
p = (caddr_t)front_kwp;
|
|
front_kwp->kw_size = back_kwp->kw_size = 0;
|
|
front_kwp->kw_other = back_kwp->kw_other = NULL;
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_kmem_free,
|
|
n, freesize, __return_address);
|
|
kmem_free(p, freesize);
|
|
}
|
|
|
|
void *
|
|
cachefs_zone_alloc(zone_t *zone, int flag)
|
|
{
|
|
#if !defined(CFS_DEBUG_ZONES)
|
|
void *ptr;
|
|
#endif
|
|
|
|
ASSERT(VALID_ADDR(zone));
|
|
ASSERT(kmem_zone_unitsize(zone));
|
|
cachefs_zone_usage += kmem_zone_unitsize(zone);
|
|
ASSERT(cachefs_zone_usage >= kmem_zone_unitsize(zone));
|
|
#ifdef CFS_DEBUG_ZONES
|
|
return (cachefs_kmem_alloc(kmem_alloc, kmem_zone_unitsize(zone), flag));
|
|
#else
|
|
ptr = kmem_zone_zalloc(zone, flag);
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_zone_alloc,
|
|
ptr, zone, __return_address);
|
|
return(ptr);
|
|
#endif
|
|
}
|
|
|
|
void *
|
|
cachefs_zone_zalloc(zone_t *zone, int flag)
|
|
{
|
|
#if !defined(CFS_DEBUG_ZONES)
|
|
void *ptr;
|
|
#endif
|
|
|
|
ASSERT(VALID_ADDR(zone));
|
|
|
|
ASSERT(kmem_zone_unitsize(zone));
|
|
cachefs_zone_usage += kmem_zone_unitsize(zone);
|
|
ASSERT(cachefs_zone_usage >= kmem_zone_unitsize(zone));
|
|
#ifdef CFS_DEBUG_ZONES
|
|
return (cachefs_kmem_alloc(kmem_zalloc, kmem_zone_unitsize(zone), flag));
|
|
#else
|
|
ptr = kmem_zone_zalloc(zone, flag);
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_zone_zalloc,
|
|
ptr, zone, __return_address);
|
|
return(ptr);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
cachefs_zone_free(zone_t *zone, void *ptr)
|
|
{
|
|
ASSERT(VALID_ADDR(zone));
|
|
ASSERT(VALID_ADDR(ptr));
|
|
|
|
ASSERT(kmem_zone_unitsize(zone));
|
|
cachefs_zone_usage -= kmem_zone_unitsize(zone);
|
|
ASSERT(cachefs_zone_usage >= 0);
|
|
#ifdef CFS_DEBUG_ZONES
|
|
|
|
cachefs_kmem_free(ptr, kmem_zone_unitsize(zone));
|
|
#else
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_zone_free,
|
|
ptr, zone, __return_address);
|
|
kmem_zone_free(zone, ptr);
|
|
#endif
|
|
}
|
|
|
|
static char *
|
|
cfs_flush_flags( int flags )
|
|
{
|
|
static char flagstr[37];
|
|
|
|
flagstr[0] = 0;
|
|
if ( flags ) {
|
|
if ( flags & CFS_INVAL ) {
|
|
strcat( flagstr, "CFS_INVAL " );
|
|
}
|
|
if ( flags & CFS_FREE ) {
|
|
strcat( flagstr, "CFS_FREE " );
|
|
}
|
|
if ( flags & CFS_TRUNC ) {
|
|
strcat( flagstr, "CFS_TRUNC" );
|
|
}
|
|
} else {
|
|
strcat( flagstr, "-NONE-" );
|
|
}
|
|
return( flagstr );
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
int
|
|
cachefs_flushvp(
|
|
bhv_desc_t *bdp,
|
|
off_t offset,
|
|
size_t len,
|
|
int flags,
|
|
uint64_t bflags )
|
|
{
|
|
int error = 0;
|
|
vnode_t *vp = BHV_TO_VNODE(bdp);
|
|
cnode_t *cp = BHVTOC(bdp);
|
|
int ospl;
|
|
u_long bsize = CFS_BMAPSIZE(cp);
|
|
|
|
CFS_DEBUG(CFSDEBUG_FLUSH,
|
|
printf(
|
|
"cachefs_flushvp: ENTER vp %p off %llx len %lx flags %s bflags %llx\n",
|
|
vp, offset, (long)len, cfs_flush_flags(flags),
|
|
bflags));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_flushvp, current_pid(),
|
|
vp, offset);
|
|
ASSERT(VALID_ADDR(vp));
|
|
ASSERT(!issplhi(getsr()));
|
|
/*
|
|
* If offset and len are both 0, then the entire file is to be flushed
|
|
* otherwise, a range is to be flushed.
|
|
* flags determines what sort of flushing is to be done. The flags
|
|
* may consist of any one of the following: CFS_INVAL, CFS_FREE, or
|
|
* CFS_TRUNC. The flags have the following meanings:
|
|
*
|
|
* CFS_INVAL - flush and toss
|
|
* CFS_FREE - flush and free, but keep for reclaim
|
|
* CFS_TRUNC - truncate
|
|
*
|
|
* CFS_INVAL and CFS_TRUNC only make sense with offset and len == 0
|
|
*/
|
|
if ( (offset == (off_t)0) && (len == (size_t)0) ) {
|
|
len = (size_t)((cp->c_size + (off_t)(bsize - 1)) &
|
|
~(off_t)(bsize - 1)) + (cachefs_readahead * bsize);
|
|
if ( flags & CFS_TRUNC ) {
|
|
CFS_DEBUG(CFSDEBUG_FLUSH,
|
|
printf("cachefs_flushvp: truncating, file size %d flush len %d "
|
|
"vpages %d\n", (int)cp->c_size, (int)len, vp->v_pgcnt));
|
|
VOP_TOSS_PAGES(vp, 0, len - 1, FI_NONE);
|
|
ASSERT(!VN_DIRTY(vp));
|
|
} else if ( flags & (CFS_INVAL | CFS_FREE) ) {
|
|
CFS_DEBUG(CFSDEBUG_FLUSH,
|
|
printf("cachefs_flushvp: invalidating and freeing, file size "
|
|
"%d flush len %d " "vpages %d\n", (int)cp->c_size,
|
|
(int)len, vp->v_pgcnt));
|
|
VOP_FLUSHINVAL_PAGES( vp, offset, offset + len - 1, FI_NONE);
|
|
/*
|
|
* Any dirty pages left are bad ones. Clean them out.
|
|
*/
|
|
if (VN_DIRTY(vp)) {
|
|
CFS_DEBUG(CFSDEBUG_FLUSH,
|
|
printf("cachefs_flushvp: dirty pages remain after "
|
|
"invalidation, vp 0x%p\n", vp));
|
|
VOP_TOSS_PAGES(vp, 0, len - 1, FI_NONE);
|
|
ASSERT(!VN_DIRTY(vp));
|
|
}
|
|
} else {
|
|
CFS_DEBUG(CFSDEBUG_FLUSH,
|
|
printf("cachefs_flushvp: flushing, file size %d flush len %d "
|
|
"vpages %d\n", (int)cp->c_size, (int)len, vp->v_pgcnt));
|
|
VOP_FLUSH_PAGES( vp, offset, offset + len - 1, bflags, FI_NONE, error );
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
if (error)
|
|
printf("cachefs_flushvp: pflushvp error %d\n", error));
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_FLUSH,
|
|
printf("cachefs_flushvp: flushed, vpages %d\n", vp->v_pgcnt));
|
|
} else {
|
|
if ( flags & (CFS_TRUNC | CFS_INVAL) ) {
|
|
error = EINVAL;
|
|
CFS_DEBUG(CFSDEBUG_FLUSH | CFSDEBUG_ERROR,
|
|
printf("cachefs_flushvp: CFS_TRUNC | CFS_INVAL "
|
|
"with non-zero offset and/or len\n"));
|
|
} else if ( flags & CFS_FREE ) {
|
|
VOP_FLUSHINVAL_PAGES( vp, offset, offset+len-1, FI_NONE );
|
|
} else {
|
|
error = chunkpush( vp, offset, offset+len-1, bflags );
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
if (error)
|
|
printf("cachefs_flushvp: chunkpush error %d\n", error));
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
if ( !error )
|
|
printf("cachefs_flushvp: flushed, file size %d flush len %d\n",
|
|
(int)cp->c_size, (int)len));
|
|
}
|
|
/*
|
|
* wait for asynchronous I/O on the cnode to complete if B_ASYNC has not
|
|
* been specified
|
|
*/
|
|
if ( !error && !(bflags & B_ASYNC) ) {
|
|
ospl = mutex_spinlock(&cp->c_iolock);
|
|
ASSERT(!(cp->c_ioflags & (CIO_ASYNCWRITE | CIO_ASYNCREAD)) ||
|
|
(cp->c_nio > 0));
|
|
while ( !error && (cp->c_ioflags & (CIO_ASYNCWRITE | CIO_ASYNCREAD)) ) {
|
|
if (error = sv_wait_sig(&cp->c_iosv, PZERO+1,
|
|
&cp->c_iolock, ospl) ) {
|
|
ASSERT(!issplhi(getsr()));
|
|
break;
|
|
}
|
|
ASSERT(!issplhi(getsr()));
|
|
ospl = mutex_spinlock(&cp->c_iolock);
|
|
}
|
|
if ( !error ) {
|
|
mutex_spinunlock(&cp->c_iolock, ospl);
|
|
#ifdef DEBUG
|
|
} else {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_flushvp: sv_wait_sig error %d\n", error));
|
|
#endif
|
|
}
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_FLUSH,
|
|
printf("cachefs_flushvp: EXIT error = %d\n", error));
|
|
return( error );
|
|
}
|
|
|
|
/*
|
|
* Construct an ascii name from a file id.
|
|
*/
|
|
void
|
|
make_ascii_name(fid_t *fidp, char *strp)
|
|
{
|
|
int i;
|
|
u_int index;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)make_ascii_name, current_pid(),
|
|
fidp, strp);
|
|
ASSERT(VALID_ADDR(fidp));
|
|
ASSERT(VALID_ADDR(strp));
|
|
for (i = 0; i < fidp->fid_len; i++) {
|
|
index = fidp->fid_data[i] & 0xf;
|
|
*strp++ = "0123456789abcdef"[index];
|
|
index = (fidp->fid_data[i] >> 4) & 0xf;
|
|
*strp++ = "0123456789abcdef"[index];
|
|
}
|
|
*strp = '\0';
|
|
}
|
|
|
|
u_int
|
|
fidhash( fid_t *fidp, u_int hashsize )
|
|
{
|
|
u_int hashval = 0;
|
|
int i;
|
|
|
|
ASSERT(VALID_ADDR(fidp));
|
|
ASSERT(fidp->fid_len <= MAXFIDSZ);
|
|
hashval = fidp->fid_len;
|
|
for ( i = 0; i < fidp->fid_len; i++ ) {
|
|
hashval += fidp->fid_data[i];
|
|
}
|
|
return( hashval % hashsize );
|
|
}
|
|
|
|
/*
|
|
* File header cache.
|
|
*/
|
|
|
|
/*
|
|
* LRU entry positions.
|
|
*/
|
|
#define TAIL 1
|
|
#define HEAD 2
|
|
|
|
/*
|
|
* Number of slots in the file header cache. This is chosen to be
|
|
* the largest prime number less than 512.
|
|
*/
|
|
#define FILEHEADER_CACHE_SLOTS 509
|
|
|
|
/*
|
|
* File header LRU list head and tail.
|
|
*/
|
|
struct filheader_cache_entry *Fileheader_lru_head = NULL;
|
|
struct filheader_cache_entry *Fileheader_lru_tail = NULL;
|
|
u_int Fileheader_lru_count = 0;
|
|
extern int fileheader_cache_size;
|
|
/*
|
|
* File header cache hash table.
|
|
*/
|
|
struct filheader_cache_entry * Fileheader_cache[FILEHEADER_CACHE_SLOTS];
|
|
/*
|
|
* Spin lock to protect file header cache and LRU.
|
|
*/
|
|
lock_t Fileheader_cache_lock;
|
|
/*
|
|
* Fileheader cache versioning to minimize search restarts after
|
|
* releasing Fileheader_cache_lock.
|
|
*/
|
|
u_int Fileheader_cache_version = 0;
|
|
/*
|
|
* number of cache entries allocated
|
|
*/
|
|
int Fileheader_cache_entries = 0;
|
|
|
|
struct filheader_cache_entry *
|
|
fileheader_lru_remove(struct filheader_cache_entry *ep)
|
|
{
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_lru_remove: ENTER ep 0x%p\n", ep));
|
|
CACHEFS_STATS->cs_fileheaders.cf_lruremoves++; \
|
|
if (ep) {
|
|
if (ep->fce_flags & ENTRY_LRU) {
|
|
if (ep->fce_lrunext) {
|
|
ep->fce_lrunext->fce_lruprev = ep->fce_lruprev;
|
|
} else {
|
|
ASSERT(ep == Fileheader_lru_tail);
|
|
Fileheader_lru_tail = ep->fce_lruprev;
|
|
}
|
|
if (ep->fce_lruprev) {
|
|
ep->fce_lruprev->fce_lrunext = ep->fce_lrunext;
|
|
} else {
|
|
ASSERT(ep == Fileheader_lru_head);
|
|
Fileheader_lru_head = ep->fce_lrunext;
|
|
}
|
|
ep->fce_lrunext = ep->fce_lruprev = NULL;
|
|
Fileheader_lru_count--;
|
|
ep->fce_flags &= ~ENTRY_LRU;
|
|
}
|
|
} else if (ep = Fileheader_lru_head) {
|
|
ASSERT(ep->fce_flags & ENTRY_LRU);
|
|
Fileheader_lru_head = ep->fce_lrunext;
|
|
if (Fileheader_lru_head) {
|
|
Fileheader_lru_head->fce_lruprev = NULL;
|
|
} else {
|
|
Fileheader_lru_tail = NULL;
|
|
}
|
|
ep->fce_lrunext = ep->fce_lruprev = NULL;
|
|
Fileheader_lru_count--;
|
|
ep->fce_flags &= ~ENTRY_LRU;
|
|
}
|
|
ASSERT(Fileheader_lru_head || !Fileheader_lru_tail);
|
|
ASSERT(!Fileheader_lru_head || Fileheader_lru_head->fce_lrunext ||
|
|
(Fileheader_lru_head == Fileheader_lru_tail));
|
|
ASSERT(Fileheader_lru_count || !Fileheader_lru_head);
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_lru_remove: EXIT ep 0x%p\n", ep));
|
|
return(ep);
|
|
}
|
|
|
|
#define FILEHEADER_LRU_ENTER(ep, loc) \
|
|
{ \
|
|
CFS_DEBUG(CFSDEBUG_SUBR, \
|
|
printf("FILEHEADER_LRU_ENTER(%s, line %d): ep 0x%p %s\n", __FILE__, \
|
|
__LINE__, ep, (loc == TAIL) ? "tail" : "head")); \
|
|
ASSERT(!(ep->fce_flags & ENTRY_LRU)); \
|
|
ASSERT(!(ep->fce_flags & ENTRY_NEW)); \
|
|
CACHEFS_STATS->cs_fileheaders.cf_lruenters++; \
|
|
if (loc == TAIL) { \
|
|
if (Fileheader_lru_tail) { \
|
|
(ep)->fce_lruprev = Fileheader_lru_tail; \
|
|
Fileheader_lru_tail->fce_lrunext = ep; \
|
|
Fileheader_lru_tail = ep; \
|
|
} else { \
|
|
ASSERT(!Fileheader_lru_head); \
|
|
Fileheader_lru_head = Fileheader_lru_tail = ep; \
|
|
(ep)->fce_lruprev = NULL; \
|
|
} \
|
|
(ep)->fce_lrunext = NULL; \
|
|
} else { \
|
|
(ep)->fce_lrunext = Fileheader_lru_head; \
|
|
(ep)->fce_lruprev = NULL; \
|
|
if (Fileheader_lru_head) { \
|
|
Fileheader_lru_head->fce_lruprev = ep; \
|
|
} \
|
|
Fileheader_lru_head = ep; \
|
|
} \
|
|
ep->fce_flags |= ENTRY_LRU; \
|
|
Fileheader_lru_count++; \
|
|
ASSERT(Fileheader_lru_head); \
|
|
ASSERT(Fileheader_lru_head->fce_lrunext || \
|
|
(Fileheader_lru_head == Fileheader_lru_tail)); \
|
|
}
|
|
|
|
#define FREE_CACHE_ENTRY(ep) \
|
|
{ \
|
|
CFS_DEBUG(CFSDEBUG_SUBR, \
|
|
printf("FREE_CACHE_ENTRY(%s, line %d): ep 0x%p\n", __FILE__, \
|
|
__LINE__, ep)); \
|
|
ASSERT(!((ep)->fce_flags & ENTRY_LRU)); \
|
|
ASSERT(!((ep)->fce_flags & ENTRY_CACHED)); \
|
|
if ((ep)->fce_header) \
|
|
CACHEFS_ZONE_FREE(Cachefs_fileheader_zone, (ep)->fce_header); \
|
|
if ((ep)->fce_fid) \
|
|
CACHEFS_ZONE_FREE(Cachefs_fid_zone, (ep)->fce_fid); \
|
|
sv_destroy(&ep->fce_wait); \
|
|
CACHEFS_ZONE_FREE(Cachefs_fhcache_zone, ep); \
|
|
Fileheader_cache_entries--; \
|
|
}
|
|
|
|
#define FILEHEADER_CACHE_ENTER(ep) \
|
|
{ \
|
|
CACHEFS_STATS->cs_fileheaders.cf_cacheenters++; \
|
|
if (Fileheader_cache[slot]) { \
|
|
Fileheader_cache[slot]->fce_prev = newent; \
|
|
} \
|
|
newent->fce_next = Fileheader_cache[slot]; \
|
|
Fileheader_cache[slot] = newent; \
|
|
newent->fce_flags |= ENTRY_CACHED; \
|
|
Fileheader_cache_version++; \
|
|
}
|
|
|
|
/*
|
|
* remove the entry from the cache
|
|
*/
|
|
#define FILEHEADER_CACHE_REMOVE(ep) \
|
|
{ \
|
|
CFS_DEBUG(CFSDEBUG_SUBR, \
|
|
printf("FILEHEADER_CACHE_REMOVE(%s, line %d): ep 0x%p\n", __FILE__, \
|
|
__LINE__, ep)); \
|
|
CACHEFS_STATS->cs_fileheaders.cf_cacheremoves++; \
|
|
if ((ep)->fce_next) { \
|
|
(ep)->fce_next->fce_prev = (ep)->fce_prev; \
|
|
} \
|
|
if ((ep)->fce_prev) { \
|
|
(ep)->fce_prev->fce_next = (ep)->fce_next; \
|
|
} else { \
|
|
Fileheader_cache[(ep)->fce_slot] = (ep)->fce_next; \
|
|
} \
|
|
(ep)->fce_flags &= ~ENTRY_CACHED; \
|
|
}
|
|
|
|
/*
|
|
* search file header cache for an entry matching the fid for the
|
|
* given vnode
|
|
* if no entry is found, a place holder is entered and all subsequest
|
|
* lookups will wait
|
|
*/
|
|
struct filheader_cache_entry *
|
|
fileheader_cache_find(vnode_t *vp)
|
|
{
|
|
int ospl;
|
|
u_int slot;
|
|
fid_t *fidp = NULL;
|
|
struct filheader_cache_entry *ep = NULL;
|
|
struct filheader_cache_entry *newent = NULL;
|
|
u_int vers;
|
|
int error;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_find: ENTER vp 0x%p\n", vp));
|
|
fidp = CACHEFS_ZONE_ALLOC(Cachefs_fid_zone, KM_SLEEP);
|
|
VOP_FID2(vp, fidp, error);
|
|
if (!error) {
|
|
slot = fidhash(fidp, FILEHEADER_CACHE_SLOTS);
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
restart:
|
|
vers = Fileheader_cache_version;
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
ASSERT((ep->fce_flags & ENTRY_DESTROY) || ep->fce_fid);
|
|
ASSERT(fidp);
|
|
if (!(ep->fce_flags & ENTRY_DESTROY) &&
|
|
FID_MATCH(fidp, ep->fce_fid)) {
|
|
CACHEFS_STATS->cs_fileheaders.cf_hits++;
|
|
ep->fce_ref++;
|
|
if (ep->fce_flags & ENTRY_NEW) {
|
|
ASSERT(!(ep->fce_flags & ENTRY_LRU));
|
|
sv_wait(&ep->fce_wait, PZERO, &Fileheader_cache_lock,
|
|
ospl);
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
if (ep->fce_flags & ENTRY_DESTROY) {
|
|
/*
|
|
* last waiter frees the entry if it is
|
|
* marked ENTRY_DESTROY
|
|
*/
|
|
if (--ep->fce_ref == 0) {
|
|
ASSERT(!ep->fce_header);
|
|
FREE_CACHE_ENTRY(ep);
|
|
}
|
|
goto restart;
|
|
} else {
|
|
ASSERT(ep->fce_header);
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
if (newent) {
|
|
ASSERT(newent->fce_fid == fidp);
|
|
FREE_CACHE_ENTRY(newent);
|
|
} else if (fidp) {
|
|
CACHEFS_ZONE_FREE(Cachefs_fid_zone, fidp);
|
|
}
|
|
ASSERT((ep->fce_flags &
|
|
(ENTRY_VALID | ENTRY_CACHED)) ==
|
|
(ENTRY_VALID | ENTRY_CACHED));
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_find: EXIT ep 0x%p\n",
|
|
ep));
|
|
return(ep);
|
|
}
|
|
} else {
|
|
(void)fileheader_lru_remove(ep);
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
if (newent) {
|
|
ASSERT(newent->fce_fid == fidp);
|
|
FREE_CACHE_ENTRY(newent);
|
|
} else if (fidp) {
|
|
CACHEFS_ZONE_FREE(Cachefs_fid_zone, fidp);
|
|
}
|
|
ASSERT((ep->fce_flags & (ENTRY_VALID | ENTRY_CACHED))
|
|
== (ENTRY_VALID | ENTRY_CACHED));
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_find: EXIT ep 0x%p\n",
|
|
ep));
|
|
return(ep);
|
|
}
|
|
}
|
|
}
|
|
if (newent) {
|
|
FILEHEADER_CACHE_ENTER(newent);
|
|
} else {
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
newent = (struct filheader_cache_entry *)CACHEFS_ZONE_ZALLOC(
|
|
Cachefs_fhcache_zone, KM_SLEEP);
|
|
Fileheader_cache_entries++;
|
|
newent->fce_flags = ENTRY_NEW;
|
|
newent->fce_fid = fidp;
|
|
newent->fce_ref = 1;
|
|
newent->fce_slot = slot;
|
|
sv_init(&newent->fce_wait, SV_DEFAULT,
|
|
"Fileheader cache entry wait");
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
if (vers != Fileheader_cache_version) {
|
|
goto restart;
|
|
} else {
|
|
FILEHEADER_CACHE_ENTER(newent);
|
|
}
|
|
}
|
|
ASSERT(newent);
|
|
ASSERT(newent->fce_fid == fidp);
|
|
ep = newent;
|
|
newent = NULL;
|
|
fidp = NULL;
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
ASSERT(ep);
|
|
} else {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("fileheader_cache_find: VOP_FID2 error for vnode 0x%p\n",
|
|
vp));
|
|
CACHEFS_ZONE_FREE(Cachefs_fid_zone, fidp);
|
|
ep = NULL;
|
|
}
|
|
CACHEFS_STATS->cs_fileheaders.cf_misses++;
|
|
ASSERT(!ep || ((ep->fce_flags & (ENTRY_NEW | ENTRY_CACHED)) ==
|
|
(ENTRY_NEW | ENTRY_CACHED)));
|
|
ASSERT(!fidp);
|
|
ASSERT(!newent);
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_find: EXIT new ep 0x%p\n", ep));
|
|
return(ep);
|
|
}
|
|
|
|
/*
|
|
* enter a new file header into the cache and wake up any procs
|
|
* waiting
|
|
*/
|
|
void
|
|
fileheader_cache_enter(struct filheader_cache_entry *ep, fileheader_t *fhp)
|
|
{
|
|
int ospl;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_enter: ENTER ep 0x%p, fhp 0x%p\n", ep, fhp));
|
|
ASSERT(!ep->fce_header);
|
|
ASSERT((ep->fce_flags & (ENTRY_NEW | ENTRY_CACHED)) ==
|
|
(ENTRY_NEW | ENTRY_CACHED));
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
Fileheader_cache_version++;
|
|
ep->fce_header = fhp;
|
|
ep->fce_flags &= ~ENTRY_NEW;
|
|
ep->fce_flags |= ENTRY_VALID;
|
|
sv_broadcast(&ep->fce_wait);
|
|
ASSERT((ep->fce_flags & (ENTRY_VALID | ENTRY_CACHED)) ==
|
|
(ENTRY_VALID | ENTRY_CACHED));
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_enter: EXIT\n"));
|
|
}
|
|
|
|
/*
|
|
* Release a file header cache entry, taking it out of the cache
|
|
* entirely.
|
|
*/
|
|
void
|
|
fileheader_cache_release(struct filheader_cache_entry *ep)
|
|
{
|
|
int ospl;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_release: ENTER ep 0x%p\n", ep));
|
|
ASSERT(!ep->fce_header);
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
/*
|
|
* decrement the reference count and check for waiters
|
|
*/
|
|
if (--ep->fce_ref) {
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_release: mark ep 0x%p destroy\n", ep));
|
|
/*
|
|
* only update the version if there are processes
|
|
* waiting on this entry
|
|
*/
|
|
Fileheader_cache_version++;
|
|
ep->fce_flags |= ENTRY_DESTROY;
|
|
sv_broadcast(&ep->fce_wait);
|
|
} else {
|
|
/*
|
|
* nothing waiting, so free the entry
|
|
*/
|
|
FREE_CACHE_ENTRY(ep);
|
|
}
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_release: EXIT\n"));
|
|
}
|
|
|
|
void
|
|
fileheader_cache_remove(vnode_t *vp)
|
|
{
|
|
int ospl;
|
|
u_int slot;
|
|
fid_t frontfid;
|
|
struct filheader_cache_entry *ep;
|
|
int error;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_remove: ENTER vp 0x%p\n", vp));
|
|
VOP_FID2(vp, &frontfid, error);
|
|
if (!error) {
|
|
CACHEFS_STATS->cs_fileheaders.cf_purges++;
|
|
slot = fidhash(&frontfid, FILEHEADER_CACHE_SLOTS);
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
if (FID_MATCH(&frontfid, ep->fce_fid)) {
|
|
if (ep->fce_ref) {
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_remove: mark ep 0x%p "
|
|
"destroy\n", ep));
|
|
ep->fce_flags |= ENTRY_DESTROY;
|
|
} else {
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
fileheader_lru_remove(ep);
|
|
FREE_CACHE_ENTRY(ep);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("fileheader_cache_remove: EXIT\n"));
|
|
}
|
|
|
|
/*
|
|
* Allocate a completely empty file header. Do this through
|
|
* fileheader_cache_find to prevent races. When the header has
|
|
* been allocated, enter it into the cache.
|
|
*/
|
|
fileheader_t *
|
|
alloc_fileheader(vnode_t *vp)
|
|
{
|
|
struct filheader_cache_entry *ep;
|
|
fileheader_t *fhp = NULL;
|
|
|
|
if (ep = fileheader_cache_find(vp)) {
|
|
if (!ep->fce_header) {
|
|
fhp = (fileheader_t *)CACHEFS_ZONE_ZALLOC(Cachefs_fileheader_zone,
|
|
KM_SLEEP);
|
|
#ifdef DEBUG
|
|
ep->fce_caller = (void *)__return_address;
|
|
#endif /* DEBUG */
|
|
fileheader_cache_enter(ep, fhp);
|
|
} else {
|
|
/*
|
|
* A file header already exists. Use it.
|
|
*/
|
|
fhp = ep->fce_header;
|
|
bzero((void *)fhp, sizeof(fileheader_t));
|
|
}
|
|
}
|
|
return(fhp);
|
|
}
|
|
|
|
static struct filheader_cache_entry *
|
|
locate_header(fileheader_t *fhp)
|
|
{
|
|
u_int slot;
|
|
struct filheader_cache_entry *ep = NULL;
|
|
int found = 0;
|
|
struct filheader_cache_entry *entry = NULL;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR, printf("locate header 0x%p\n", fhp));
|
|
for (slot = 0; slot < FILEHEADER_CACHE_SLOTS; slot++) {
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
if (ep->fce_header == fhp) {
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf(" ep 0x%p: fid 0x%p ref %d flags 0x%x\n", ep,
|
|
ep->fce_fid, ep->fce_ref, ep->fce_flags));
|
|
found++;
|
|
entry = ep;
|
|
}
|
|
}
|
|
}
|
|
ASSERT((found == 0) || (found == 1));
|
|
return(entry);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
char *
|
|
entryflags_to_str(u_int flags)
|
|
{
|
|
static char str[59];
|
|
|
|
str[0] = '\0';
|
|
if (flags & ENTRY_NEW) {
|
|
strcat(str, "ENTRY_NEW ");
|
|
}
|
|
if (flags & ENTRY_DESTROY) {
|
|
strcat(str, "ENTRY_DESTROY ");
|
|
}
|
|
if (flags & ENTRY_VALID) {
|
|
strcat(str, "ENTRY_VALID ");
|
|
}
|
|
if (flags & ENTRY_LRU) {
|
|
strcat(str, "ENTRY_LRU ");
|
|
}
|
|
if (flags & ENTRY_CACHED) {
|
|
strcat(str, "ENTRY_CACHED ");
|
|
}
|
|
return(str);
|
|
}
|
|
|
|
static void
|
|
print_cache_entry(struct filheader_cache_entry *ep, int slot)
|
|
{
|
|
char *sym;
|
|
off_t offset;
|
|
|
|
qprintf("Cache entry 0x%p:\n", ep);
|
|
qprintf(" header: 0x%p\n", ep->fce_header);
|
|
qprintf(" fid: 0x%p\n", ep->fce_fid);
|
|
qprintf(" ref: %d\n", ep->fce_ref);
|
|
qprintf(" flags: %s\n",
|
|
entryflags_to_str(ep->fce_flags));
|
|
qprintf(" slot: %d (expected %d)\n", ep->fce_slot,
|
|
slot);
|
|
sym = fetch_kname(ep->fce_caller, (void *)&offset);
|
|
qprintf(" caller: %s+0x%llx [0x%p]\n",
|
|
sym ? sym : "unknown", offset, ep->fce_caller);
|
|
}
|
|
|
|
void
|
|
idbg_headercache( __psint_t addr )
|
|
{
|
|
struct filheader_cache_entry *ep = NULL;
|
|
int slot;
|
|
|
|
if (addr == -1) {
|
|
for (slot = 0; slot < FILEHEADER_CACHE_SLOTS; slot++) {
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
print_cache_entry(ep, slot);
|
|
}
|
|
}
|
|
} else if (addr < -1) {
|
|
ep = locate_header((fileheader_t *)addr);
|
|
if (ep) {
|
|
print_cache_entry(ep, ep->fce_slot);
|
|
} else {
|
|
qprintf("File header 0x%p not in file header cache\n",
|
|
(void *)addr);
|
|
}
|
|
} else {
|
|
qprintf("Invalid fileheader address 0x%p.\n", addr);
|
|
}
|
|
}
|
|
|
|
static int
|
|
find_fileheader_by_vnode(vnode_t *vp, fileheader_t *fhp)
|
|
{
|
|
fid_t frontfid;
|
|
u_int slot;
|
|
int ospl;
|
|
struct filheader_cache_entry *ep = NULL;
|
|
int found = 0;
|
|
int error = 0;
|
|
|
|
ASSERT(VALID_ADDR(vp));
|
|
ASSERT(VALID_ADDR(fhp));
|
|
VOP_FID2(vp, &frontfid, error);
|
|
if (!error) {
|
|
slot = fidhash(&frontfid, FILEHEADER_CACHE_SLOTS);
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
if (ep->fce_header == fhp) {
|
|
ASSERT(FID_MATCH(&frontfid, ep->fce_fid));
|
|
found++;
|
|
}
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_ERROR, if (!found) (void)locate_header(fhp));
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
}
|
|
ASSERT((found == 1) || (found == 0));
|
|
return(found);
|
|
}
|
|
|
|
int
|
|
find_fileheader_by_fid(fid_t *fidp, fileheader_t *fhp)
|
|
{
|
|
u_int slot;
|
|
int ospl;
|
|
struct filheader_cache_entry *ep = NULL;
|
|
int found = 0;
|
|
|
|
ASSERT(VALID_ADDR(fidp));
|
|
ASSERT(VALID_ADDR(fhp));
|
|
slot = fidhash(fidp, FILEHEADER_CACHE_SLOTS);
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
if (ep->fce_header == fhp) {
|
|
ASSERT(FID_MATCH(fidp, ep->fce_fid));
|
|
found++;
|
|
}
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_ERROR, if (!found) (void)locate_header(fhp));
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
ASSERT((found == 1) || (found == 0));
|
|
return(found);
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
/*
|
|
* Release a reference on a file header. If the reference count goes to
|
|
* zero, put the file header onto the file header LRU. If the number of
|
|
* entries on the LRU equals or exceeds the maximum allowed, take the
|
|
* first entry from the LRU and free it.
|
|
*/
|
|
void
|
|
release_fileheader(fid_t *fidp, fileheader_t *fhp)
|
|
{
|
|
u_int slot;
|
|
int ospl;
|
|
struct filheader_cache_entry *ep = NULL;
|
|
struct filheader_cache_entry *relp;
|
|
|
|
ASSERT(VALID_ADDR(fhp));
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("release_fileheader: ENTER fidp 0x%p fhp 0x%p\n", fidp, fhp));
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
if (!fidp)
|
|
printf("release_fileheader(line %d): NULL front FID\n", __LINE__));
|
|
CACHEFS_STATS->cs_fileheaders.cf_releases++;
|
|
if (!fhp) {
|
|
return;
|
|
}
|
|
if (fidp) {
|
|
slot = fidhash(fidp, FILEHEADER_CACHE_SLOTS);
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
if (ep->fce_header == fhp) {
|
|
ASSERT(FID_MATCH(fidp, ep->fce_fid));
|
|
if (!--ep->fce_ref) {
|
|
if (ep->fce_flags & ENTRY_DESTROY) {
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("release_fileheader: ep 0x%p destroy\n",
|
|
ep));
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
FREE_CACHE_ENTRY(ep);
|
|
} else if (fileheader_cache_size == 0) {
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
FREE_CACHE_ENTRY(ep);
|
|
} else if (Fileheader_lru_count >= fileheader_cache_size) {
|
|
relp = fileheader_lru_remove(NULL);
|
|
ASSERT(relp);
|
|
ASSERT(!relp->fce_ref);
|
|
ASSERT(relp != ep);
|
|
FILEHEADER_CACHE_REMOVE(relp);
|
|
FREE_CACHE_ENTRY(relp);
|
|
FILEHEADER_LRU_ENTER(ep, TAIL);
|
|
} else {
|
|
FILEHEADER_LRU_ENTER(ep, TAIL);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!ep && (ep = locate_header(fhp))) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("release_fileheader: ep 0x%p missed in fid lookup\n",
|
|
ep));
|
|
if (!--ep->fce_ref) {
|
|
if (ep->fce_flags & ENTRY_DESTROY) {
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("release_fileheader: ep 0x%p destroy\n", ep));
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
FREE_CACHE_ENTRY(ep);
|
|
} else if (fileheader_cache_size == 0) {
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
FREE_CACHE_ENTRY(ep);
|
|
} else if (Fileheader_lru_count >= fileheader_cache_size) {
|
|
relp = fileheader_lru_remove(NULL);
|
|
ASSERT(relp);
|
|
ASSERT(!relp->fce_ref);
|
|
ASSERT(relp != ep);
|
|
FILEHEADER_CACHE_REMOVE(relp);
|
|
FREE_CACHE_ENTRY(relp);
|
|
FILEHEADER_LRU_ENTER(ep, TAIL);
|
|
} else {
|
|
FILEHEADER_LRU_ENTER(ep, TAIL);
|
|
}
|
|
}
|
|
}
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
} else {
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
for (slot = 0; slot < FILEHEADER_CACHE_SLOTS; slot++) {
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
if (ep->fce_header == fhp) {
|
|
if (!--ep->fce_ref) {
|
|
if (ep->fce_flags & ENTRY_DESTROY) {
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
FREE_CACHE_ENTRY(ep);
|
|
} else if (fileheader_cache_size == 0) {
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
FREE_CACHE_ENTRY(ep);
|
|
} else if (Fileheader_lru_count >=
|
|
fileheader_cache_size) {
|
|
relp = fileheader_lru_remove(NULL);
|
|
ASSERT(relp);
|
|
ASSERT(!relp->fce_ref);
|
|
FILEHEADER_CACHE_REMOVE(relp);
|
|
FREE_CACHE_ENTRY(relp);
|
|
FILEHEADER_LRU_ENTER(ep, TAIL);
|
|
} else {
|
|
FILEHEADER_LRU_ENTER(ep, TAIL);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("release_fileheader: EXIT\n"));
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* ARGSUSED */
|
|
void
|
|
idbg_checklru( __psint_t addr )
|
|
{
|
|
int lrucount = 0;
|
|
int cachecount = 0;
|
|
int newcount = 0;
|
|
int destroycount = 0;
|
|
int validcount = 0;
|
|
int slot;
|
|
struct filheader_cache_entry *ep;
|
|
|
|
for (slot = 0; slot < FILEHEADER_CACHE_SLOTS; slot++) {
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
if (ep->fce_flags & ENTRY_LRU) {
|
|
lrucount++;
|
|
} else {
|
|
print_cache_entry(ep, slot);
|
|
}
|
|
if (ep->fce_flags & ENTRY_NEW) {
|
|
newcount++;
|
|
}
|
|
if (ep->fce_flags & ENTRY_DESTROY) {
|
|
destroycount++;
|
|
}
|
|
if (ep->fce_flags & ENTRY_VALID) {
|
|
validcount++;
|
|
}
|
|
if (ep->fce_flags & ENTRY_CACHED) {
|
|
cachecount++;
|
|
}
|
|
}
|
|
}
|
|
qprintf("%d entries marked LRU\n%d entries marked cached\n"
|
|
"%d entries marked new\n%d entries marked destroy\n"
|
|
"%d entries marked valid\nLRU count %d, cache entry count %d\n",
|
|
lrucount, cachecount, newcount, destroycount, validcount,
|
|
Fileheader_lru_count, Fileheader_cache_entries);
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
void
|
|
cachefs_mem_check(void)
|
|
{
|
|
int ospl;
|
|
struct filheader_cache_entry *ep;
|
|
struct filheader_cache_entry *relp;
|
|
int slot;
|
|
|
|
if (Fileheader_lru_count != Fileheader_cache_entries) {
|
|
cmn_err(CE_WARN, "CacheFS: file header LRU count (%d) not equal "
|
|
"to file header cache entry count (%d)\n", Fileheader_lru_count,
|
|
Fileheader_cache_entries);
|
|
ospl = mutex_spinlock(&Fileheader_cache_lock);
|
|
restart:
|
|
for (slot = 0; slot < FILEHEADER_CACHE_SLOTS; slot++) {
|
|
for (ep = Fileheader_cache[slot]; ep; ep = ep->fce_next) {
|
|
if (!(ep->fce_flags & ENTRY_LRU)) {
|
|
ep->fce_ref = 0;
|
|
if (ep->fce_flags & ENTRY_DESTROY) {
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("release_fileheader: ep 0x%p destroy\n",
|
|
ep));
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
FREE_CACHE_ENTRY(ep);
|
|
} else if (fileheader_cache_size == 0) {
|
|
FILEHEADER_CACHE_REMOVE(ep);
|
|
FREE_CACHE_ENTRY(ep);
|
|
} else if (Fileheader_lru_count >= fileheader_cache_size) {
|
|
relp = fileheader_lru_remove(NULL);
|
|
ASSERT(relp);
|
|
ASSERT(!relp->fce_ref);
|
|
ASSERT(relp != ep);
|
|
FILEHEADER_CACHE_REMOVE(relp);
|
|
FREE_CACHE_ENTRY(relp);
|
|
FILEHEADER_LRU_ENTER(ep, TAIL);
|
|
} else {
|
|
FILEHEADER_LRU_ENTER(ep, TAIL);
|
|
}
|
|
goto restart;
|
|
}
|
|
}
|
|
}
|
|
mutex_spinunlock(&Fileheader_cache_lock, ospl);
|
|
}
|
|
}
|
|
|
|
/* Calculate a 16 bit ones-complement checksum for a single buffer.
|
|
* This routine always adds even address bytes to the high order 8 bits
|
|
* of the 16 bit checksum and odd address bytes are added to the low
|
|
* order 8 bits of the 16 bit checksum. The caller must swap bytes in
|
|
* the sum to make this correct.
|
|
*
|
|
* The caller must ensure that the length is not zero or > 32K.
|
|
*/
|
|
static u_int
|
|
cksum(ushort *src, /* first byte */
|
|
int len) /* # of bytes */
|
|
{
|
|
u_int ck = 0;
|
|
|
|
ASSERT(!((__psint_t)src & 1));
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cksum, current_pid(), src,
|
|
len);
|
|
ASSERT(VALID_ADDR(src));
|
|
ASSERT(VALID_ADDR((__psint_t)src + (__psint_t)len));
|
|
/* do 64 byte blocks for the 128-byte cache line of the IP17 */
|
|
while (len >= 64) {
|
|
ck += src[0]; ck += src[1]; ck += src[2]; ck += src[3];
|
|
ck += src[4]; ck += src[5]; ck += src[6]; ck += src[7];
|
|
|
|
ck += src[8]; ck += src[9]; ck += src[10]; ck += src[11];
|
|
ck += src[12]; ck += src[13]; ck += src[14]; ck += src[15];
|
|
|
|
ck += src[16]; ck += src[17]; ck += src[18]; ck += src[19];
|
|
ck += src[20]; ck += src[21]; ck += src[22]; ck += src[23];
|
|
|
|
ck += src[24]; ck += src[25]; ck += src[26]; ck += src[27];
|
|
ck += src[28]; ck += src[29]; ck += src[30]; ck += src[31];
|
|
src += 32;
|
|
len -= 64;
|
|
}
|
|
|
|
/* we have < 64 bytes remaining */
|
|
if (0 != (len&32)) {
|
|
ck += src[0]; ck += src[1]; ck += src[2]; ck += src[3];
|
|
ck += src[4]; ck += src[5]; ck += src[6]; ck += src[7];
|
|
ck += src[8]; ck += src[9]; ck += src[10]; ck += src[11];
|
|
ck += src[12]; ck += src[13]; ck += src[14]; ck += src[15];
|
|
src += 16;
|
|
}
|
|
if (0 != (len&16)) {
|
|
ck += src[0]; ck += src[1]; ck += src[2]; ck += src[3];
|
|
ck += src[4]; ck += src[5]; ck += src[6]; ck += src[7];
|
|
src += 8;
|
|
}
|
|
if (0 != (len&8)) {
|
|
ck += src[0]; ck += src[1]; ck += src[2]; ck += src[3];
|
|
src += 4;
|
|
}
|
|
|
|
if (0 != (len&4)) {
|
|
ck += src[0]; ck += src[1];
|
|
src += 2;
|
|
}
|
|
|
|
if (0 != (len&2)) {
|
|
ck += src[0];
|
|
src += 1;
|
|
}
|
|
|
|
if (0 != (len&1)) {
|
|
#ifdef _MIPSEL
|
|
ck += *(unchar*)src;
|
|
#else
|
|
ck += *(unchar*)src << 8;
|
|
#endif
|
|
}
|
|
|
|
ck = (ck & 0xffff) + (ck >> 16);
|
|
return(0xffff & (ck + (ck >> 16)));
|
|
}
|
|
|
|
int
|
|
cachefs_write_file_header(cnode_t *cp, vnode_t *vp, fileheader_t *fhp,
|
|
int fh_blocksize, int alignment)
|
|
{
|
|
int seg;
|
|
int wflags;
|
|
int ospl;
|
|
int error = 0;
|
|
struct uio uio;
|
|
iovec_t iov;
|
|
caddr_t pbuf = NULL;
|
|
caddr_t mem = NULL;
|
|
int allocsize = fh_blocksize + alignment - 1;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR | CFSDEBUG_WRITEHDR,
|
|
printf("cachefs_write_file_header: ENTER cp 0x%p vp 0x%p allocents "
|
|
"%d\n", cp, vp, (int)fhp->fh_metadata.md_allocents));
|
|
CACHEFUNC_TRACE(CFTRACE_WRITEHDR, (void *)cachefs_write_file_header,
|
|
cp, vp, fhp->fh_metadata.md_allocents);
|
|
ASSERT(valid_file_header(fhp, NULL));
|
|
ASSERT(!cp || VALID_ADDR(cp));
|
|
ASSERT(VALID_ADDR(vp));
|
|
ASSERT(VALID_ADDR(fhp));
|
|
ASSERT(allocsize >= sizeof(fileheader_t));
|
|
/*
|
|
* By default, we use the caller's memory as the output buffer.
|
|
* If we must allocate an intermediate buffer, it will be done below.
|
|
* Allocate directly through kmem_alloc as opposed to using the cachefs
|
|
* allocation wrappers. This is because the allocated address must be
|
|
* cache aligned. (It may also have to be aligned to some other value.)
|
|
*/
|
|
pbuf = (caddr_t)fhp;
|
|
if (!(alignment & (alignment - 1))) {
|
|
/*
|
|
* If the file header supplied by the caller is not already aligned,
|
|
* we must allocate an aligned buffer and do bcopy.
|
|
*/
|
|
if (cp || (__psunsigned_t)pbuf & (__psunsigned_t)(alignment - 1)) {
|
|
pbuf = mem = kmem_alloc(allocsize, KM_SLEEP | KM_CACHEALIGN);
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_write_file_header,
|
|
mem, allocsize, 0);
|
|
/*
|
|
* alignment is a power of 2, so align the buffer the fast way
|
|
* If the caller has supplied a cnode, operations on the file
|
|
* header must be protected. Rather than hold a mutex across the
|
|
* write below, just use an intermediate buffer.
|
|
*/
|
|
if ((__psunsigned_t)pbuf & (__psunsigned_t)(alignment - 1)) {
|
|
pbuf = (caddr_t)(((__psunsigned_t)pbuf +
|
|
(__psunsigned_t)(alignment - 1)) &
|
|
~((__psunsigned_t)(alignment - 1)));
|
|
}
|
|
}
|
|
ASSERT(!((__psunsigned_t)pbuf & (__psunsigned_t)(alignment - 1)));
|
|
} else if (cp || (__psunsigned_t)pbuf % (__psunsigned_t)(alignment)){
|
|
pbuf = mem = kmem_alloc(allocsize, KM_SLEEP | KM_CACHEALIGN);
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_write_file_header,
|
|
mem, allocsize, 0);
|
|
/*
|
|
* alignment is not a power of 2, so align the buffer the long
|
|
* way (multiplication & division)
|
|
* again, we only do this if the buffer is not already properly
|
|
* aligned
|
|
*/
|
|
pbuf = (caddr_t)((((__psunsigned_t)pbuf +
|
|
(__psunsigned_t)(alignment - 1)) * (__psunsigned_t)alignment) /
|
|
(__psunsigned_t)alignment);
|
|
ASSERT(!((__psunsigned_t)pbuf % (__psunsigned_t)(alignment)));
|
|
}
|
|
ASSERT(VALID_ADDR(pbuf));
|
|
ASSERT(((__psint_t)pbuf + fh_blocksize) <= ((__psint_t)mem + allocsize));
|
|
/*
|
|
* If a cnode was supplied, we must do some locking for protection. We
|
|
* assume that if cp is supplied, then the file header pointed to by
|
|
* fhp is actually cp->c_fileheader.
|
|
* If no cnode was supplied, the file header pointer can be assumed to
|
|
* be private to the caller.
|
|
*/
|
|
if (cp) {
|
|
ASSERT(mem);
|
|
ASSERT((caddr_t)pbuf != (caddr_t)fhp);
|
|
ASSERT(((__psint_t)pbuf + FILEHEADER_SIZE) <=
|
|
((__psint_t)mem + allocsize));
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
bcopy((caddr_t)fhp, pbuf, FILEHEADER_SIZE);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
} else if (mem) {
|
|
/*
|
|
* Copy the file header data to the aligned buffer for writing.
|
|
*/
|
|
ASSERT(((__psint_t)pbuf + FILEHEADER_SIZE) <=
|
|
((__psint_t)mem + allocsize));
|
|
bcopy((caddr_t)fhp, pbuf, FILEHEADER_SIZE);
|
|
}
|
|
ASSERT(valid_file_header((fileheader_t *)pbuf, NULL));
|
|
/*
|
|
* Calculate the checksum for the file header. Do this over the entire
|
|
* header with the checksum field itself being 0.
|
|
* Only calculate the sum for the header portion (FILEHEADER_SIZE), the
|
|
* remainder of the block is of undefined contents.
|
|
*/
|
|
((fileheader_t *)pbuf)->fh_metadata.md_checksum = 0;
|
|
((fileheader_t *)pbuf)->fh_metadata.md_checksum = cksum((ushort *)pbuf,
|
|
sizeof(fileheader_t));
|
|
ASSERT(((fileheader_t *)pbuf)->fh_metadata.md_checksum != 0);
|
|
ASSERT(valid_file_header((fileheader_t *)pbuf, NULL));
|
|
CFS_DEBUG(CFSDEBUG_VALIDATE,
|
|
ASSERT(valid_file_header((fileheader_t *)pbuf,
|
|
cp ? cp->c_backfid : NULL)));
|
|
if (front_dio && (fhp->fh_metadata.md_attributes.ca_type == VREG)) {
|
|
seg = UIO_NOSPACE;
|
|
wflags = IO_SYNC | IO_DIRECT | IO_TRUSTEDDIO;
|
|
} else {
|
|
seg = UIO_SYSSPACE;
|
|
wflags = 0;
|
|
}
|
|
UIO_SETUP(&uio, &iov, pbuf, fh_blocksize, (off_t)0, seg, FWRITE,
|
|
RLIM_INFINITY);
|
|
/*
|
|
* Use IO_SYNC to make sure the underlying file system updates
|
|
* everything before returning. For local file systems such as
|
|
* XFS and EFS, this means updating the inode on disk.
|
|
* Use IO_DIRECT to make the file system not use the buffer cache.
|
|
*/
|
|
WRITE_VP(vp, &uio, wflags, sys_cred, &curuthread->ut_flid, error);
|
|
if (!error && uio.uio_resid) {
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
error = EIO;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_write_file_header(line %d): incomplete write of "
|
|
"file header, resid %d\n", __LINE__, (int)uio.uio_resid));
|
|
#ifdef DEBUG
|
|
} else if (error) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_write_file_header(line %d): file header write "
|
|
"error %d\n", __LINE__, error));
|
|
ASSERT(uio.uio_sigpipe == 0);
|
|
#endif
|
|
}
|
|
ASSERT(cachefs_zone_validate((caddr_t)fhp, fh_blocksize));
|
|
if (mem) {
|
|
kmem_free(mem, allocsize);
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_write_file_header,
|
|
mem, allocsize, 1);
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_write_file_header: EXIT error %d allocents %d\n", error,
|
|
(int)fhp->fh_metadata.md_allocents));
|
|
return(error);
|
|
}
|
|
|
|
int
|
|
cachefs_write_header(cnode_t *cp, int force)
|
|
{
|
|
int error = 0;
|
|
int ospl;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_write_header: ENTER cp %p\n", cp));
|
|
ASSERT(VALID_ADDR(cp));
|
|
CACHEFS_STATS->cs_fileheaders.cf_writes++;
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, cachefs_write_header, current_pid(),
|
|
cp, 0);
|
|
CNODE_TRACE(CNTRACE_WRITEHDR, cp, (void *)cachefs_write_header,
|
|
cp->c_flags, cp->c_fileheader->fh_metadata.md_allocents);
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
/*
|
|
* Don't bother updating files which have been marked CN_NOCACHE.
|
|
* The exception is cnodes marked CN_NEEDINVAL. These must be
|
|
* updated if a front file exists.
|
|
* Don't attempt to update files for which there is no front file.
|
|
* Sometimes cnodes don't have file headers (c_fileheader is NULL);
|
|
* just ignore them.
|
|
*/
|
|
if (!(cp->c_flags & CN_UPDATED) || (cp->c_flags & CN_UPDATE_PENDING)) {
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
return(0);
|
|
} else if ((((cp->c_flags & (CN_NOCACHE | CN_NEEDINVAL)) == CN_NOCACHE) &&
|
|
!force)) {
|
|
cp->c_flags &= ~CN_UPDATED;
|
|
CNODE_TRACE(CNTRACE_UPDATE, cp, (void *)cachefs_write_header,
|
|
cp->c_flags, cp->c_fileheader->fh_metadata.md_allocents);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
return(0);
|
|
}
|
|
ASSERT( VALID_ADDR(cp->c_fileheader) );
|
|
cp->c_flags &= ~CN_UPDATED;
|
|
cp->c_flags |= CN_UPDATE_PENDING;
|
|
CNODE_TRACE(CNTRACE_UPDATE, cp, (void *)cachefs_write_header,
|
|
cp->c_flags, cp->c_fileheader->fh_metadata.md_allocents);
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
if (cp->c_frontvp || !(error = cachefs_getfrontvp(cp))) {
|
|
error = cachefs_write_file_header(cp, cp->c_frontvp, cp->c_fileheader,
|
|
FILEHEADER_BLOCK_SIZE(C_TO_FSCACHE(cp)->fs_cache),
|
|
DIO_ALIGNMENT(C_TO_FSCACHE(cp)->fs_cache));
|
|
} else {
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefs_write_header: nocache cp 0x%p on "
|
|
"error %d from cachefs_getfrontvp\n", cp, error));
|
|
cachefs_nocache(cp);
|
|
}
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
if (error) {
|
|
cp->c_flags |= CN_NOCACHE;
|
|
CNODE_TRACE(CNTRACE_NOCACHE, cp, cachefs_write_header,
|
|
(int)cp->c_flags, 0);
|
|
CACHEFS_STATS->cs_nocache++;
|
|
CFS_DEBUG(CFSDEBUG_NOCACHE,
|
|
printf("cachefs_write_header(line %d): nocache cp 0x%p, error %d\n",
|
|
__LINE__, cp, error));
|
|
}
|
|
cp->c_flags &= ~CN_UPDATE_PENDING;
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
CFS_DEBUG(CFSDEBUG_VALIDATE,
|
|
if (!error && cp)
|
|
validate_fileheader(cp, __FILE__, __LINE__));
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_write_header: EXIT error %d\n", error));
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* It is assumed that the file header pointed to by fhp is isolated (i.e.,
|
|
* it is not pointed to by any cnode). Thus, no locking is necessary.
|
|
*/
|
|
/* ARGSUSED */
|
|
int
|
|
cachefs_read_file_header(vnode_t *vp, fileheader_t **fhp, vtype_t vtype,
|
|
cachefscache_t *cachep)
|
|
{
|
|
int seg;
|
|
int rflags;
|
|
struct uio uio;
|
|
iovec_t iov;
|
|
int error = 0;
|
|
u_int header_sum = 0;
|
|
u_int calculated_sum = 0;
|
|
caddr_t pbuf = NULL;
|
|
caddr_t mem = NULL;
|
|
struct filheader_cache_entry *cache_entry;
|
|
int alignment = DIO_ALIGNMENT(cachep);
|
|
int minio = DIO_MINIO(cachep);
|
|
int fh_blocksize = FILEHEADER_BLOCK_SIZE(cachep);
|
|
int allocsize = fh_blocksize + alignment + minio;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_read_file_header: ENTER vp %p\n", vp));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_read_file_header,
|
|
current_pid(), vp, fhp);
|
|
ASSERT(VALID_ADDR(vp));
|
|
ASSERT(VALID_ADDR(fhp));
|
|
ASSERT(allocsize >= sizeof(fileheader_t));
|
|
CACHEFS_STATS->cs_fileheaders.cf_reads++;
|
|
cache_entry = fileheader_cache_find(vp);
|
|
if (!cache_entry) {
|
|
return(ENOSYS);
|
|
}
|
|
if (*fhp = cache_entry->fce_header) {
|
|
return(0);
|
|
}
|
|
ASSERT(cache_entry->fce_flags & ENTRY_NEW);
|
|
#ifdef DEBUG
|
|
cache_entry->fce_caller = (void *)__return_address;
|
|
#endif /* DEBUG */
|
|
/*
|
|
* allocate a properly aligned buffer
|
|
* assume that the zone is set up so that allocations will always
|
|
* be properly aligned
|
|
*/
|
|
*fhp = (fileheader_t *)CACHEFS_ZONE_ALLOC(Cachefs_fileheader_zone,
|
|
KM_SLEEP);
|
|
pbuf = (caddr_t)*fhp;
|
|
if (!(alignment & (alignment - 1))) {
|
|
/*
|
|
* If the file header supplied by the caller is not already aligned,
|
|
* we must allocate an aligned buffer and do bcopy.
|
|
*/
|
|
#ifdef MH_R10000_SPECULATION_WAR
|
|
/*
|
|
* On R10000 O2s, we have to pay attention to the I/O length
|
|
* constraint.
|
|
*/
|
|
if (((__psunsigned_t)pbuf & (__psunsigned_t)(alignment - 1)) ||
|
|
(fh_blocksize % minio)) {
|
|
#else /* MH_R10000_SPECULATION_WAR */
|
|
if ((__psunsigned_t)pbuf & (__psunsigned_t)(alignment - 1)) {
|
|
#endif /* MH_R10000_SPECULATION_WAR */
|
|
pbuf = mem = kmem_zalloc(allocsize, KM_SLEEP | KM_CACHEALIGN);
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_read_file_header,
|
|
mem, allocsize, 0);
|
|
/*
|
|
* alignment is a power of 2, so align the buffer the fast way
|
|
* but only align if pbuf is not already properly aligned
|
|
*/
|
|
if ((__psunsigned_t)pbuf & (__psunsigned_t)(alignment - 1)) {
|
|
pbuf = (caddr_t)(((__psunsigned_t)pbuf +
|
|
(__psunsigned_t)(alignment - 1)) &
|
|
~((__psunsigned_t)(alignment - 1)));
|
|
}
|
|
fh_blocksize = ((fh_blocksize + minio - 1) / minio) * minio;
|
|
}
|
|
ASSERT(!((__psunsigned_t)pbuf & (__psunsigned_t)(alignment - 1)));
|
|
#ifdef MH_R10000_SPECULATION_WAR
|
|
ASSERT((fh_blocksize % minio) == 0);
|
|
#endif /* MH_R10000_SPECULATION_WAR */
|
|
} else if ((__psunsigned_t)pbuf % (__psunsigned_t)(alignment)){
|
|
pbuf = mem = kmem_zalloc(allocsize, KM_SLEEP | KM_CACHEALIGN);
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_read_file_header,
|
|
mem, allocsize, 0);
|
|
/*
|
|
* alignment is not a power of 2, so align the buffer the long
|
|
* way (multiplication & division)
|
|
* again, we only do this if the buffer is not already properly
|
|
* aligned
|
|
*/
|
|
pbuf = (caddr_t)((((__psunsigned_t)pbuf +
|
|
(__psunsigned_t)(alignment - 1)) * (__psunsigned_t)alignment) /
|
|
(__psunsigned_t)alignment);
|
|
fh_blocksize = ((fh_blocksize + minio - 1) / minio) * minio;
|
|
ASSERT(!((__psunsigned_t)pbuf % (__psunsigned_t)(alignment)));
|
|
ASSERT((fh_blocksize % minio) == 0);
|
|
}
|
|
ASSERT(VALID_ADDR(pbuf));
|
|
ASSERT(((__psint_t)pbuf + fh_blocksize) <= ((__psint_t)mem + allocsize));
|
|
/*
|
|
* read the file header into the buffer
|
|
*/
|
|
if (front_dio && ((vtype == VREG) || (vtype == VNON))) {
|
|
seg = UIO_NOSPACE;
|
|
rflags = IO_SYNC | IO_DIRECT | IO_TRUSTEDDIO;
|
|
} else {
|
|
seg = UIO_SYSSPACE;
|
|
rflags = 0;
|
|
}
|
|
UIO_SETUP(&uio, &iov, pbuf, fh_blocksize, (off_t)0,
|
|
seg, FREAD, RLIM_INFINITY);
|
|
READ_VP(vp, &uio, rflags, sys_cred, &curuthread->ut_flid, error);
|
|
if (!error) {
|
|
if ((fh_blocksize - uio.uio_resid) < FILEHEADER_SIZE) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_read_file_header(line %d): incomplete read of "
|
|
"file header, resid %d\n", __LINE__, (int)uio.uio_resid));
|
|
CACHEFS_STATS->cs_badheader.cbh_short++;
|
|
error = EIO;
|
|
CACHEFS_ZONE_FREE(Cachefs_fileheader_zone, *fhp);
|
|
} else {
|
|
/*
|
|
* copy the file header data into the area supplied by the
|
|
* caller, only copying FILEHEADER_SIZE bytes
|
|
* this is only done if an intermediate buffer is used, in
|
|
* which case, mem will be non-NULL
|
|
*/
|
|
if (mem) {
|
|
bcopy(pbuf, (caddr_t)*fhp, FILEHEADER_SIZE);
|
|
}
|
|
if ((*fhp)->fh_metadata.md_checksum == 0) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_read_file_header(line %d): file header "
|
|
"checksum is 0\n", __LINE__));
|
|
(*fhp)->fh_metadata.md_state = 0;
|
|
CACHEFS_STATS->cs_badheader.cbh_checksum++;
|
|
error = EIO;
|
|
CACHEFS_ZONE_FREE(Cachefs_fileheader_zone, *fhp);
|
|
} else {
|
|
header_sum = (u_long)(*fhp)->fh_metadata.md_checksum;
|
|
(*fhp)->fh_metadata.md_checksum = 0;
|
|
calculated_sum = cksum((ushort *)*fhp, sizeof(fileheader_t));
|
|
if (calculated_sum != header_sum) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_read_file_header(line %d): bad file "
|
|
"header checksum, got %d (0x%x) expected %d\n",
|
|
__LINE__, (int)header_sum, (int)header_sum,
|
|
(int)calculated_sum));
|
|
(*fhp)->fh_metadata.md_state = 0;
|
|
CACHEFS_STATS->cs_badheader.cbh_checksum++;
|
|
error = EIO;
|
|
CACHEFS_ZONE_FREE(Cachefs_fileheader_zone, *fhp);
|
|
} else if (!valid_file_header(*fhp, (fid_t *)NULL)) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_read_file_header(line %d): invalid "
|
|
"file header\n", __LINE__));
|
|
(*fhp)->fh_metadata.md_state = 0;
|
|
CACHEFS_STATS->cs_badheader.cbh_data++;
|
|
error = EIO;
|
|
CACHEFS_ZONE_FREE(Cachefs_fileheader_zone, *fhp);
|
|
} else {
|
|
fileheader_cache_enter(cache_entry, *fhp);
|
|
}
|
|
}
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_FILEHEADER,
|
|
if (*fhp && (*fhp)->fh_metadata.md_state & MD_INIT)
|
|
printf("cachefs_read_file_header(line %d): uninitialized "
|
|
"file header\n", __LINE__));
|
|
} else {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_read_file_header(line %d): file header read "
|
|
"error %d\n", __LINE__, error));
|
|
CACHEFS_STATS->cs_badheader.cbh_readerr++;
|
|
CACHEFS_ZONE_FREE(Cachefs_fileheader_zone, *fhp);
|
|
}
|
|
if (mem) {
|
|
kmem_free(mem, allocsize);
|
|
CACHEFUNC_TRACE(CFTRACE_ALLOC, (void *)cachefs_read_file_header,
|
|
mem, allocsize, 1);
|
|
}
|
|
if (error) {
|
|
ASSERT(!*fhp);
|
|
fileheader_cache_release(cache_entry);
|
|
}
|
|
ASSERT(cachefs_zone_validate((caddr_t)*fhp, fh_blocksize));
|
|
ASSERT(!*fhp || find_fileheader_by_vnode(vp, *fhp));
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_read_file_header: EXIT error %d\n", error));
|
|
return(error);
|
|
}
|
|
|
|
int
|
|
cachefs_lookup_frontdir(vnode_t *dvp, fid_t *backfid, vnode_t **frontdirvp)
|
|
{
|
|
char *name;
|
|
int error;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_lookup_frontdir: ENTER vp %p\n", dvp));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_lookup_frontdir,
|
|
current_pid(), dvp, backfid);
|
|
ASSERT(VALID_ADDR(dvp));
|
|
ASSERT(VALID_ADDR(backfid));
|
|
ASSERT(VALID_ADDR(frontdirvp));
|
|
name = CACHEFS_KMEM_ALLOC(backfid->fid_len * 2 + 1, KM_SLEEP);
|
|
make_ascii_name(backfid, name);
|
|
ASSERT(strlen(name) < backfid->fid_len * 2 + 1);
|
|
VOP_LOOKUP(dvp, name, frontdirvp, (struct pathname *)NULL, 0,
|
|
(vnode_t *)NULL, sys_cred, error);
|
|
if (error && (error != ENOENT)) {
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_lookup_frontdir(line %d): front directory "
|
|
"lookup error, file %s: %d\n", __LINE__, name, error));
|
|
}
|
|
CACHEFS_KMEM_FREE(name, backfid->fid_len * 2 + 1);
|
|
ASSERT( error || *frontdirvp );
|
|
ASSERT( !error || !*frontdirvp );
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_lookup_frontdir: EXIT error %d\n", error));
|
|
return(error);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
int
|
|
cachefs_name_is_legal(char *name)
|
|
{
|
|
int legal = 1;
|
|
|
|
ASSERT(VALID_ADDR(name));
|
|
if (name) {
|
|
if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) {
|
|
legal = 0;
|
|
} else {
|
|
while (*name) {
|
|
if (*name == '/') {
|
|
legal = 0;
|
|
break;
|
|
}
|
|
name++;
|
|
}
|
|
}
|
|
} else {
|
|
legal = 0;
|
|
}
|
|
return(legal);
|
|
}
|
|
#endif
|
|
|
|
int
|
|
cachefs_create_frontdir(cachefscache_t *cachep, vnode_t *dvp, fid_t *backfid,
|
|
vnode_t **frontdirvp)
|
|
{
|
|
char *name;
|
|
int error;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_create_frontdir: ENTER cachep %p, vnode 0x%p\n",
|
|
cachep, dvp));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_create_frontdir,
|
|
current_pid(), cachep, dvp);
|
|
ASSERT(VALID_ADDR(cachep));
|
|
ASSERT(VALID_ADDR(dvp));
|
|
ASSERT(VALID_ADDR(backfid));
|
|
ASSERT(VALID_ADDR(frontdirvp));
|
|
name = CACHEFS_KMEM_ALLOC(backfid->fid_len * 2 + 1, KM_SLEEP);
|
|
make_ascii_name(backfid, name);
|
|
ASSERT(strlen(name) < backfid->fid_len * 2 + 1);
|
|
ASSERT(cachefs_name_is_legal(name));
|
|
error = cachefs_allocfile(cachep);
|
|
if (!error) {
|
|
VOP_MKDIR(dvp, name, &Cachefs_dir_attr, frontdirvp, sys_cred,
|
|
error);
|
|
if (error) {
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
if (error != EEXIST)
|
|
printf("cachefs_create_frontdir(line %d): error creating "
|
|
"front directory, file %s: %d\n", __LINE__,
|
|
name, error));
|
|
}
|
|
} else {
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_create_frontdir(line %d): error allocating "
|
|
"front directory, file %s: %d\n", __LINE__, name, error));
|
|
}
|
|
CACHEFS_KMEM_FREE(name, backfid->fid_len * 2 + 1);
|
|
ASSERT( error || *frontdirvp );
|
|
ASSERT( !error || !*frontdirvp );
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_create_frontdir: EXIT error %d\n", error));
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* create a front file
|
|
*/
|
|
int
|
|
cachefs_create_frontfile( cachefscache_t *cachep, vnode_t *dvp, char *name,
|
|
vnode_t **frontvp, fid_t *backfid, fileheader_t **fhpp, vtype_t vtype )
|
|
{
|
|
int error = 0;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_create_frontfile: ENTER cachep %p, vnode 0x%p, "
|
|
"name %s\n", cachep, dvp, name));
|
|
ASSERT(vtype != VNON);
|
|
CACHEFUNC_TRACE(CFTRACE_CREATE, (void *)cachefs_create_frontfile,
|
|
current_pid(), cachep, dvp);
|
|
ASSERT(VALID_ADDR(cachep));
|
|
ASSERT(VALID_ADDR(dvp));
|
|
ASSERT(VALID_ADDR(name));
|
|
ASSERT(VALID_ADDR(frontvp));
|
|
ASSERT(!backfid || VALID_ADDR(backfid));
|
|
ASSERT(VALID_ADDR(fhpp));
|
|
ASSERT(cachefs_name_is_legal(name));
|
|
/*
|
|
* First, allocate the file and enough blocks for the file header.
|
|
* This step is merely internal accounting.
|
|
*/
|
|
error = cachefs_allocfile(cachep);
|
|
if (!error) {
|
|
/*
|
|
* the file header occupies a block of data defined by the minimum
|
|
* direct I/O transfer size
|
|
*/
|
|
error = cachefs_allocblocks(cachep, FILEHEADER_BLOCK_SIZE(cachep),
|
|
sys_cred);
|
|
if (!error) {
|
|
/*
|
|
* Now, create the front file, using the standard attributes.
|
|
* If this succeeds, set the attributes to make sure that the
|
|
* file size has been properly set. We cannot, unfortuantely,
|
|
* assume that the underlying file system has done this in the
|
|
* creation of the file.
|
|
*/
|
|
*frontvp = NULL;
|
|
VOP_CREATE(dvp, name, &Cachefs_file_attr, 1,
|
|
VREAD | VWRITE, frontvp, sys_cred, error);
|
|
switch (error) {
|
|
case EEXIST:
|
|
ASSERT(!issplhi(getsr()));
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_create_frontfile(line %d): front file "
|
|
"create error, file %s: EEXIST\n", __LINE__, name));
|
|
VOP_LOOKUP(dvp, name, frontvp,
|
|
(struct pathname *)NULL, 0,
|
|
(vnode_t *)NULL, sys_cred,
|
|
error);
|
|
if (!error) {
|
|
error = cachefs_read_file_header(*frontvp, fhpp,
|
|
vtype, cachep);
|
|
if (error) {
|
|
VN_RELE(*frontvp);
|
|
*frontvp = NULL;
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_create_frontfile(line %d): "
|
|
"error reading file header for file %s: "
|
|
"%d\n", __LINE__, name, error));
|
|
}
|
|
}
|
|
ASSERT(!issplhi(getsr()));
|
|
break;
|
|
case 0:
|
|
ASSERT(!issplhi(getsr()));
|
|
*fhpp = alloc_fileheader(*frontvp);
|
|
if (!*fhpp) {
|
|
VN_RELE(*frontvp);
|
|
*frontvp = NULL;
|
|
error = ENOSYS;
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_create_frontfile(line %d): "
|
|
"error allocating file header for file %s: "
|
|
"%d\n", __LINE__, name, error));
|
|
break;
|
|
}
|
|
if (backfid) {
|
|
(*fhpp)->fh_metadata.md_backfid.fid_len =
|
|
backfid->fid_len;
|
|
bcopy(backfid->fid_data,
|
|
&(*fhpp)->fh_metadata.md_backfid.fid_data,
|
|
backfid->fid_len);
|
|
(*fhpp)->fh_metadata.md_state = MD_INIT | MD_MDVALID;
|
|
} else {
|
|
(*fhpp)->fh_metadata.md_state = MD_INIT;
|
|
}
|
|
(*fhpp)->fh_metadata.md_attributes.ca_type = vtype;
|
|
ASSERT(!issplhi(getsr()));
|
|
break;
|
|
default:
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_create_frontfile(line %d): front file "
|
|
"create error, file %s: %d\n", __LINE__, name,
|
|
error));
|
|
break;
|
|
}
|
|
} else {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_create_frontfile(line %d): front block "
|
|
"allocation error for %d bytes, file %s: %d\n", __LINE__,
|
|
FILEHEADER_BLOCK_SIZE(cachep), name, error));
|
|
}
|
|
#ifdef DEBUG
|
|
} else {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_create_frontfile(line %d): front file allocation "
|
|
"error, file %s: %d\n", __LINE__, name, error));
|
|
#endif
|
|
}
|
|
/*
|
|
* We return with no error and the front file(s) held (v_count > 1) or
|
|
* with an error and *frontvp NULL.
|
|
*/
|
|
ASSERT( error || *frontvp );
|
|
ASSERT( !error || (*frontvp == NULL) );
|
|
ASSERT( error || (*frontvp)->v_count );
|
|
/*
|
|
* If there was no error, *fhpp must be non-NULL, otherwise it must
|
|
* be NULL.
|
|
*/
|
|
ASSERT( error || *fhpp );
|
|
ASSERT( !error || (*fhpp == NULL) );
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_create_frontfile: EXIT error %d\n", error));
|
|
return( error );
|
|
}
|
|
|
|
int
|
|
cachefs_remove_frontfile(vnode_t *dvp, char *nm)
|
|
{
|
|
vnode_t *vp = NULL;
|
|
int error = 0;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_remove_frontfile: ENTER dvp %p, name %s\n", dvp, nm));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_remove_frontfile,
|
|
current_pid(), dvp, nm);
|
|
ASSERT(VALID_ADDR(dvp));
|
|
ASSERT(VALID_ADDR(nm));
|
|
ASSERT(cachefs_name_is_legal(nm));
|
|
VOP_LOOKUP(dvp, nm, &vp, NULL, 0, NULL, sys_cred, error);
|
|
switch (error) {
|
|
case ENOENT:
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_remove_frontfile: EXIT error %d\n", error));
|
|
return(0);
|
|
case 0:
|
|
fileheader_cache_remove(vp);
|
|
VN_RELE(vp);
|
|
break;
|
|
default:
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_remove_front(line %d): error looking up front "
|
|
"file, file %s: %d\n", __LINE__, nm, error));
|
|
}
|
|
VOP_REMOVE(dvp, nm, sys_cred, error);
|
|
switch (error) {
|
|
case ENOENT:
|
|
error = 0;
|
|
case 0:
|
|
break;
|
|
default:
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_remove_front(line %d): error removing front "
|
|
"file, file %s: %d\n", __LINE__, nm, error));
|
|
break;
|
|
}
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_remove_frontfile: EXIT error %d\n", error));
|
|
return( error );
|
|
}
|
|
|
|
static int
|
|
cachefs_remove_frontdir_contents(fscache_t *fscp, char *dirname)
|
|
{
|
|
uio_t uio;
|
|
iovec_t iov;
|
|
int error = 0;
|
|
int eof = 0;
|
|
char *dirbuf = NULL;
|
|
int len;
|
|
dirent_t *dep;
|
|
vnode_t *dvp;
|
|
|
|
VOP_LOOKUP(fscp->fs_cacheidvp, dirname, &dvp, NULL, 0, NULL, sys_cred,
|
|
error);
|
|
if (error) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_remove_frontdir_contents(line %d): error "
|
|
"looking up front directory, file %s: %d\n",
|
|
__LINE__, dirname, error));
|
|
return(error);
|
|
}
|
|
dirbuf = CACHEFS_KMEM_ALLOC(fscp->fs_bmapsize, KM_SLEEP);
|
|
do {
|
|
UIO_SETUP(&uio, &iov, dirbuf, fscp->fs_bmapsize, (off_t)0, UIO_SYSSPACE,
|
|
0, RLIM_INFINITY);
|
|
VOP_READDIR(dvp, &uio, sys_cred, &eof, error);
|
|
if (!error) {
|
|
len = fscp->fs_bmapsize - uio.uio_resid;
|
|
if (!len) {
|
|
break;
|
|
}
|
|
dep = (dirent_t *)dirbuf;
|
|
while (len && !error) {
|
|
ASSERT(len >= (int)DIRENTSIZE(1));
|
|
ASSERT(len >= dep->d_reclen);
|
|
/*
|
|
* if the name is not . or .., remove it
|
|
*/
|
|
if (strcmp(dep->d_name, ".") && strcmp(dep->d_name, "..")) {
|
|
VOP_REMOVE(dvp, dep->d_name, sys_cred, error);
|
|
CFS_DEBUG(CFSDEBUG_ERROR, if (error)
|
|
printf("cachefs_remove_frontdir_contents(line %d): "
|
|
"error removing front directory, file %s: %d\n",
|
|
__LINE__, dep->d_name, error));
|
|
}
|
|
len -= (int)dep->d_reclen;
|
|
dep = (dirent_t *)((u_long)dep + (u_long)dep->d_reclen);
|
|
}
|
|
}
|
|
} while(!error && !eof);
|
|
CACHEFS_KMEM_FREE(dirbuf, fscp->fs_bmapsize);
|
|
VN_RELE(dvp);
|
|
return(error);
|
|
}
|
|
|
|
int
|
|
cachefs_remove_frontdir(fscache_t *fscp, fid_t *backfid, int rem_contents)
|
|
{
|
|
int error = 0;
|
|
char *dirname;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_remove_frontdir: ENTER fscp 0x%p, backfid 0x%p\n",
|
|
fscp, backfid));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_remove_frontdir,
|
|
current_pid(), fscp, backfid);
|
|
ASSERT(VALID_ADDR(fscp));
|
|
ASSERT(VALID_ADDR(backfid));
|
|
ASSERT(backfid->fid_len <= MAXFIDSZ);
|
|
if (backfid->fid_len == 0) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_remove_frontdir(line %d): empty file ID\n",
|
|
__LINE__));
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_remove_frontdir: EXIT error 0\n"));
|
|
return(0);
|
|
}
|
|
dirname = CACHEFS_KMEM_ALLOC(backfid->fid_len * 2 + 1, KM_SLEEP);
|
|
make_ascii_name(backfid, dirname);
|
|
VOP_RMDIR(fscp->fs_cacheidvp, dirname, curuthread->ut_cdir,
|
|
sys_cred, error);
|
|
switch (error) {
|
|
case ENOENT:
|
|
error = 0;
|
|
case 0:
|
|
case EINTR:
|
|
break;
|
|
case EEXIST:
|
|
if (rem_contents) {
|
|
/*
|
|
* non-empty directory
|
|
* remove its contents
|
|
*/
|
|
error = cachefs_remove_frontdir_contents(fscp, dirname);
|
|
if (!error) {
|
|
VOP_RMDIR(fscp->fs_cacheidvp, dirname,
|
|
curuthread->ut_cdir,
|
|
sys_cred, error);
|
|
if (!error)
|
|
break;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_remove_frontdir(line %d): error "
|
|
"removing front directory, file %s: %d\n",
|
|
__LINE__, dirname, error));
|
|
}
|
|
/*
|
|
* on error, drop through to the default case
|
|
*/
|
|
} else {
|
|
break;
|
|
}
|
|
default:
|
|
CACHEFS_STATS->cs_fronterror++;
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_remove_frontdir(line %d): error removing "
|
|
"front directory, file %s: %d\n", __LINE__, dirname,
|
|
error));
|
|
break;
|
|
}
|
|
CACHEFS_KMEM_FREE(dirname, backfid->fid_len * 2 + 1);
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_remove_frontdir: EXIT error %d\n", error));
|
|
return( error );
|
|
}
|
|
|
|
/*
|
|
* Returns the tod in secs when the consistency of the object should
|
|
* be checked.
|
|
* This function is used by both the single writer and strict consistency
|
|
* modes.
|
|
*/
|
|
u_long
|
|
cachefs_gettime_cached_object(struct fscache *fscp, vtype_t vtype, u_long mtime)
|
|
{
|
|
u_long xsec;
|
|
u_long acmin, acmax;
|
|
time_t mytime = time;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_gettime_cached_object: ENTER fscp 0x%p, mtime %d\n",
|
|
fscp, mtime));
|
|
ASSERT(vtype != VNON);
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_gettime_cached_object,
|
|
current_pid(), fscp, vtype);
|
|
ASSERT(VALID_ADDR(fscp));
|
|
/*
|
|
* Expire time is based on the number of seconds since the last change
|
|
* (i.e. files that changed recently are likely to change soon),
|
|
*/
|
|
if (vtype == VDIR) {
|
|
acmin = fscp->fs_acdirmin;
|
|
acmax = fscp->fs_acdirmax;
|
|
} else {
|
|
acmin = fscp->fs_acregmin;
|
|
acmax = fscp->fs_acregmax;
|
|
}
|
|
|
|
xsec = mytime - mtime;
|
|
xsec = MAX(xsec, acmin);
|
|
xsec = MIN(xsec, acmax);
|
|
xsec += mytime;
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_gettime_cached_object: EXIT exp time %d\n", xsec));
|
|
return (xsec);
|
|
}
|
|
|
|
int
|
|
await_population(cnode_t *cp)
|
|
{
|
|
int ospl;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)await_population, current_pid(),
|
|
cp, 0);
|
|
ASSERT(VALID_ADDR(cp));
|
|
ospl = mutex_spinlock(&cp->c_statelock);
|
|
while (cp->c_flags & CN_POPULATION_PENDING) {
|
|
if (sv_wait_sig(&cp->c_popwait_sv, (PZERO+1), &cp->c_statelock,
|
|
ospl)) {
|
|
ASSERT(!issplhi(getsr()));
|
|
return(EINTR);
|
|
}
|
|
ASSERT(!issplhi(getsr()));
|
|
(void)mutex_spinlock(&cp->c_statelock);
|
|
}
|
|
mutex_spinunlock(&cp->c_statelock, ospl);
|
|
return(0);
|
|
}
|
|
|
|
int
|
|
cachefs_update_directory( cnode_t *dcp, int flag, cred_t *cr )
|
|
{
|
|
struct cachefs_req *rp;
|
|
int ospl;
|
|
int error = 0;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_update_directory,
|
|
current_pid(), dcp, flag);
|
|
ASSERT(VALID_ADDR(dcp));
|
|
ASSERT(VALID_ADDR(cr));
|
|
ASSERT(RW_WRITE_HELD(&dcp->c_rwlock));
|
|
ASSERT(!issplhi(getsr()));
|
|
ospl = mutex_spinlock(&dcp->c_statelock);
|
|
if (C_CACHING(dcp)) {
|
|
if (dcp->c_flags & CN_POPULATION_PENDING) {
|
|
ASSERT(VALID_ADDR(dcp->c_fileheader));
|
|
/*
|
|
* If this is a synchronous update, wait for the in-progress
|
|
* population.
|
|
*/
|
|
if (flag & IO_SYNC) {
|
|
do {
|
|
rw_exit(&dcp->c_rwlock);
|
|
if (sv_wait_sig(&dcp->c_popwait_sv, (PZERO+1),
|
|
&dcp->c_statelock, ospl)) {
|
|
ASSERT(!issplhi(getsr()));
|
|
rw_enter(&dcp->c_rwlock, RW_WRITER);
|
|
return(EINTR);
|
|
}
|
|
ASSERT(!issplhi(getsr()));
|
|
rw_enter(&dcp->c_rwlock, RW_WRITER);
|
|
(void)mutex_spinlock(&dcp->c_statelock);
|
|
} while (dcp->c_flags & CN_POPULATION_PENDING);
|
|
/*
|
|
* It is possible that CN_POPULATION_PENDING has been turned
|
|
* off but MD_POPULATED is still not set.
|
|
*/
|
|
if (!(dcp->c_fileheader->fh_metadata.md_state & MD_POPULATED)) {
|
|
dcp->c_flags |= CN_POPULATION_PENDING;
|
|
mutex_spinunlock(&dcp->c_statelock, ospl);
|
|
ASSERT(!issplhi(getsr()));
|
|
dnlc_purge_vp(CTOV(dcp));
|
|
error = cachefs_populate_dir(dcp, cr);
|
|
ASSERT(RW_WRITE_HELD(&dcp->c_rwlock));
|
|
ASSERT(error ||
|
|
(dcp->c_fileheader->fh_metadata.md_state &
|
|
MD_POPULATED));
|
|
} else {
|
|
mutex_spinunlock(&dcp->c_statelock, ospl);
|
|
}
|
|
} else {
|
|
mutex_spinunlock(&dcp->c_statelock, ospl);
|
|
}
|
|
ASSERT(!issplhi(getsr()));
|
|
} else {
|
|
dcp->c_fileheader->fh_metadata.md_state &= ~MD_POPULATED;
|
|
dcp->c_flags |= (CN_POPULATION_PENDING | CN_UPDATED);
|
|
CNODE_TRACE(CNTRACE_UPDATE, dcp, (void *)cachefs_update_directory,
|
|
dcp->c_flags, dcp->c_fileheader->fh_metadata.md_allocents);
|
|
ASSERT(valid_file_header(dcp->c_fileheader, NULL));
|
|
mutex_spinunlock(&dcp->c_statelock, ospl);
|
|
if (!dcp->c_frontvp) {
|
|
error = cachefs_getfrontvp(dcp);
|
|
CFS_DEBUG(CFSDEBUG_ERROR, if (error)
|
|
printf("cachefs_update_directory(line %d): error %d "
|
|
"getting front vnode\n", __LINE__, error));
|
|
if (error) {
|
|
(void)cachefs_nocache(dcp);
|
|
error = 0;
|
|
}
|
|
}
|
|
ASSERT(!issplhi(getsr()));
|
|
dnlc_purge_vp(CTOV(dcp));
|
|
if (flag & IO_SYNC) {
|
|
error = cachefs_populate_dir(dcp, cr);
|
|
ASSERT(RW_WRITE_HELD(&dcp->c_rwlock));
|
|
ASSERT(error ||
|
|
(dcp->c_fileheader->fh_metadata.md_state & MD_POPULATED));
|
|
} else {
|
|
rp = (struct cachefs_req *)
|
|
CACHEFS_ZONE_ZALLOC(Cachefs_req_zone, KM_SLEEP);
|
|
rp->cfs_req_u.cu_popfile.cpf_cp = dcp;
|
|
VN_HOLD(CTOV(dcp));
|
|
CNODE_TRACE(CNTRACE_HOLD, dcp, (void *)cachefs_update_directory,
|
|
CTOV(dcp)->v_count, 0);
|
|
rp->cfs_cr = cr;
|
|
crhold(rp->cfs_cr);
|
|
rp->cfs_cmd = CFS_POPULATE_DIR;
|
|
error = cachefs_addqueue(rp,
|
|
&C_TO_FSCACHE(dcp)->fs_cache->c_workq);
|
|
if (error) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_update_directory: cachefs_addqueue "
|
|
"error %d\n", error));
|
|
VN_RELE(CTOV(dcp));
|
|
CNODE_TRACE(CNTRACE_HOLD, dcp,
|
|
(void *)cachefs_update_directory, CTOV(dcp)->v_count,
|
|
0);
|
|
ospl = mutex_spinlock(&dcp->c_statelock);
|
|
dcp->c_flags &= ~CN_POPULATION_PENDING;
|
|
mutex_spinunlock(&dcp->c_statelock, ospl);
|
|
CACHEFS_ZONE_FREE(Cachefs_req_zone, rp);
|
|
error = 0;
|
|
}
|
|
}
|
|
ASSERT(!issplhi(getsr()));
|
|
}
|
|
} else {
|
|
mutex_spinunlock(&dcp->c_statelock, ospl);
|
|
}
|
|
ASSERT(RW_WRITE_HELD(&dcp->c_rwlock));
|
|
return( error );
|
|
}
|
|
|
|
int
|
|
cachefs_irix5_fmtdirent(void **buf, struct dirent *dep, int count)
|
|
{
|
|
register struct irix5_dirent *gdp = (struct irix5_dirent *)*buf;
|
|
register ino_t fileno = dep->d_ino;
|
|
register off_t offset = dep->d_off;
|
|
register ssize_t reclen = dep->d_reclen;
|
|
register u_short namelen = reclen - DIRENTBASESIZE;
|
|
|
|
CFS_DEBUG(CFSDEBUG_SUBR,
|
|
printf("cachefs_irix5_fmtdirent: namelen = %d, reclen = %d\n",
|
|
namelen, (int)reclen));
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_irix5_fmtdirent,
|
|
current_pid(), buf, dep);
|
|
ASSERT(VALID_ADDR(buf));
|
|
ASSERT(VALID_ADDR(dep));
|
|
if ((reclen < DIRENTSIZE(1)) || (namelen > MAXPATHLEN) ||
|
|
(reclen > count)) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_irix5_fmtdirent: invalid entry found: reclen "
|
|
"%d, namelen %d, count %d, offset %d\n", (int)reclen,
|
|
(int)namelen, (int)count, (int)offset));
|
|
return(EINVAL);
|
|
}
|
|
/*
|
|
* In order to keep the d_off values valid, we leave d_reclen the same
|
|
* as in the 64-bit directory entry. All that changes is the size of
|
|
* d_off and the positioning of fields.
|
|
*/
|
|
gdp->d_reclen = reclen;
|
|
gdp->d_ino = fileno;
|
|
gdp->d_off = offset;
|
|
bcopy((caddr_t)dep->d_name, (caddr_t)gdp->d_name, namelen + 1);
|
|
#ifdef DEBUG
|
|
dep = (dirent_t *)((u_long)dep + (u_long)reclen);
|
|
count -= (int)reclen;
|
|
if ((count >= DIRENTSIZE(1)) && (dep->d_reclen < DIRENTSIZE(1))) {
|
|
CFS_DEBUG(CFSDEBUG_ERROR,
|
|
printf("cachefs_irix5_fmtdirent: next entry invalid: reclen "
|
|
"%d, new dep 0x%p, buf 0x%p, *buf 0x%p, count %d\n",
|
|
(int)dep->d_reclen, dep, buf, *buf,
|
|
count));
|
|
}
|
|
#endif
|
|
*buf = (void *)((u_long)gdp + (u_long)gdp->d_reclen);
|
|
return(0);
|
|
}
|
|
|
|
int
|
|
cachefs_search_dir(cnode_t *dcp, char *nm, lockmode_t lm)
|
|
{
|
|
caddr_t dirbuf;
|
|
uio_t uio;
|
|
iovec_t iov;
|
|
int size;
|
|
int ospl;
|
|
dirent_t *dep;
|
|
int error = 0;
|
|
int count;
|
|
|
|
CACHEFUNC_TRACE(CFTRACE_OTHER, (void *)cachefs_search_dir,
|
|
current_pid(), dcp, nm);
|
|
ASSERT(VALID_ADDR(dcp));
|
|
ASSERT(VALID_ADDR(nm));
|
|
ASSERT(C_CACHING(dcp));
|
|
ASSERT(VALID_ADDR(dcp->c_fileheader));
|
|
/*
|
|
* Do not wait for a pending population here. That would cause a
|
|
* deadlock. If a population is pending, MD_POPULATED will not be set
|
|
* in the metadata.
|
|
*/
|
|
ospl = mutex_spinlock(&dcp->c_statelock);
|
|
if (dcp->c_fileheader->fh_metadata.md_state & MD_POPULATED) {
|
|
size = dcp->c_fileheader->fh_allocmap[0].am_size;
|
|
ASSERT(size > 0);
|
|
mutex_spinunlock(&dcp->c_statelock, ospl);
|
|
dirbuf = CACHEFS_KMEM_ALLOC(size, KM_SLEEP);
|
|
UIO_SETUP(&uio, &iov, dirbuf, size,
|
|
(off_t)DATA_START(C_TO_FSCACHE(dcp)), UIO_SYSSPACE, 0,
|
|
RLIM_INFINITY);
|
|
error = FRONT_READ_VP(dcp, &uio, 0, sys_cred);
|
|
count = size - (int)uio.uio_resid;
|
|
ASSERT(valid_dirents((dirent_t *)dirbuf, (int)count));
|
|
if (!error && count) {
|
|
dep = (dirent_t *)dirbuf;
|
|
error = ENOENT;
|
|
while ((count >= (int)(DIRENTSIZE(1))) &&
|
|
(count >= (int)dep->d_reclen)) {
|
|
if ((dep->d_reclen < DIRENTSIZE(1)) ||
|
|
((dep->d_reclen - DIRENTBASESIZE) > MAXPATHLEN)) {
|
|
(void)cachefs_inval_object(dcp, lm);
|
|
error = 0;
|
|
break;
|
|
}
|
|
if (strcmp(dep->d_name, nm) == 0) {
|
|
error = 0;
|
|
break;
|
|
}
|
|
count -= dep->d_reclen;
|
|
dep = (dirent_t *)((u_long)dep + (u_long)dep->d_reclen);
|
|
}
|
|
}
|
|
CACHEFS_KMEM_FREE(dirbuf, size);
|
|
} else {
|
|
mutex_spinunlock(&dcp->c_statelock, ospl);
|
|
error = 0;
|
|
}
|
|
return(error);
|
|
}
|
|
|
|
int
|
|
cachefs_sys(int cmd, void *argp, rval_t *rvp)
|
|
{
|
|
int error = 0;
|
|
|
|
switch (cmd) {
|
|
case CACHEFSSYS_REPLACEMENT:
|
|
error = cachefs_replacement(argp, rvp);
|
|
break;
|
|
case CACHEFSSYS_CHECKFID:
|
|
error = cachefs_checkfid(argp, rvp);
|
|
break;
|
|
case CACHEFSSYS_RECONNECT:
|
|
error = cachefs_reconnect(argp, rvp);
|
|
break;
|
|
case CACHEFSSYS_MOUNT:
|
|
error = cachefs_complete_mount(argp, rvp);
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
}
|
|
return(error);
|
|
}
|