4109 lines
98 KiB
C
Executable File
4109 lines
98 KiB
C
Executable File
/*
|
|
* Copyright (c) 1992,1994, by Sun Microsystems, Inc.
|
|
* All rights reserved.
|
|
*/
|
|
#pragma ident "@(#)cachefs_vnops.c 1.181 95/08/28 SMI"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/cred.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/user.h>
|
|
#include <sys/time.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/file.h>
|
|
#include <sys/filio.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/tiuser.h>
|
|
#include <sys/pathname.h>
|
|
#include <sys/dirent.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/debug.h>
|
|
#include <sys/vmsystm.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/flock.h>
|
|
#include <sys/swap.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <sys/disp.h>
|
|
#include <sys/kmem.h>
|
|
#include <sys/cmn_err.h>
|
|
#include <sys/vtrace.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/bootconf.h>
|
|
#include <sys/dnlc.h>
|
|
|
|
#include <vm/hat.h>
|
|
#include <vm/as.h>
|
|
#include <vm/page.h>
|
|
#include <vm/pvn.h>
|
|
#include <vm/seg.h>
|
|
#include <vm/seg_map.h>
|
|
#include <vm/seg_vn.h>
|
|
#include <vm/rm.h>
|
|
#include <sys/fs/cachefs_fs.h>
|
|
#include <sys/fs/cachefs_log.h>
|
|
#include "fs/fs_subr.h"
|
|
|
|
extern int basyncnt;
|
|
extern kmutex_t cachefs_cnode_freelist_lock;
|
|
|
|
static int cachefs_open(register struct vnode **, int, cred_t *);
|
|
static int cachefs_close(struct vnode *, int, int, offset_t, cred_t *);
|
|
static int cachefs_read(struct vnode *, struct uio *, int, cred_t *);
|
|
static int cachefs_write(struct vnode *, struct uio *, int, cred_t *);
|
|
static int cachefs_ioctl(struct vnode *, int, int, int, cred_t *, int *);
|
|
static int cachefs_getattr(struct vnode *, struct vattr *, int, cred_t *);
|
|
static int cachefs_setattr(register struct vnode *, struct vattr *,
|
|
int, cred_t *);
|
|
static int cachefs_access(struct vnode *, int, int, cred_t *);
|
|
static int cachefs_lookup(struct vnode *, char *, struct vnode **,
|
|
struct pathname *, int, struct vnode *, cred_t *);
|
|
static int cachefs_create(struct vnode *, char *, struct vattr *,
|
|
enum vcexcl, int, struct vnode **, cred_t *);
|
|
static int cachefs_remove(struct vnode *, char *, cred_t *);
|
|
static int cachefs_link(struct vnode *, struct vnode *, char *, cred_t *);
|
|
static int cachefs_rename(struct vnode *, char *, struct vnode *,
|
|
char *, cred_t *);
|
|
static int cachefs_mkdir(struct vnode *, char *, register struct vattr *,
|
|
struct vnode **, cred_t *);
|
|
static int cachefs_rmdir(struct vnode *, char *, struct vnode *, cred_t *);
|
|
static int cachefs_readdir(struct vnode *, register struct uio *,
|
|
cred_t *, int *);
|
|
static int cachefs_symlink(struct vnode *, char *, struct vattr *, char *,
|
|
cred_t *);
|
|
static int cachefs_readlink(struct vnode *, struct uio *, cred_t *);
|
|
int cachefs_fsync(struct vnode *, int, cred_t *);
|
|
static void cachefs_inactive_queue(register struct vnode *, cred_t *);
|
|
static int cachefs_fid(struct vnode *, struct fid *);
|
|
static void cachefs_rwlock(struct vnode *, int);
|
|
static void cachefs_rwunlock(struct vnode *, int);
|
|
static int cachefs_seek(struct vnode *, offset_t, offset_t *);
|
|
static int cachefs_cmp(struct vnode *, struct vnode *);
|
|
static int cachefs_frlock(struct vnode *, int, struct flock *,
|
|
int, offset_t, cred_t *);
|
|
static int cachefs_space(struct vnode *, int, struct flock *, int,
|
|
offset_t, cred_t *);
|
|
static int cachefs_realvp(struct vnode *, struct vnode **);
|
|
static int cachefs_getpage(struct vnode *, offset_t, u_int, u_int *,
|
|
struct page *[], u_int, struct seg *, caddr_t,
|
|
enum seg_rw, cred_t *);
|
|
static int cachefs_getapage(struct vnode *, u_int, u_int, u_int *,
|
|
struct page *[], u_int, struct seg *, caddr_t,
|
|
enum seg_rw, cred_t *);
|
|
static int cachefs_putpage(struct vnode *, offset_t, u_int, int, cred_t *);
|
|
int cachefs_putapage(struct vnode *, page_t *, u_int *, u_int *,
|
|
int, cred_t *);
|
|
void cachefs_async_stop(register struct vfs *);
|
|
static int cachefs_map(struct vnode *, offset_t, struct as *, caddr_t *,
|
|
u_int, u_char, u_char, u_int, cred_t *);
|
|
static int cachefs_addmap(struct vnode *, offset_t, struct as *, caddr_t,
|
|
u_int, u_char, u_char, u_int, cred_t *);
|
|
static int cachefs_delmap(struct vnode *, offset_t, struct as *, caddr_t,
|
|
u_int, u_int, u_int, u_int, cred_t *);
|
|
|
|
static int cachefs_dump(struct vnode *, caddr_t, int, int);
|
|
static int cachefs_pageio(struct vnode *, page_t *,
|
|
u_int, u_int, int, cred_t *);
|
|
static int cachefs_setsecattr(vnode_t *, vsecattr_t *, int, cred_t *);
|
|
static int cachefs_getsecattr(vnode_t *, vsecattr_t *, int, cred_t *);
|
|
static int cachefs_writepage(struct vnode *vp, caddr_t base,
|
|
int tcount, struct uio *uiop);
|
|
static int cachefs_pin(struct vnode *vp, cred_t *cr);
|
|
static int cachefs_pin_locked(struct vnode *vp, cred_t *cr);
|
|
static int cachefs_unpin(struct vnode *vp, cred_t *cr);
|
|
static int cachefs_convert_mount(struct fscache *fscp,
|
|
struct cachefs_cnvt_mnt *ccmp);
|
|
|
|
struct vnodeops cachefs_vnodeops = {
|
|
cachefs_open,
|
|
cachefs_close,
|
|
cachefs_read,
|
|
cachefs_write,
|
|
cachefs_ioctl,
|
|
fs_setfl,
|
|
cachefs_getattr,
|
|
cachefs_setattr,
|
|
cachefs_access,
|
|
cachefs_lookup,
|
|
cachefs_create,
|
|
cachefs_remove,
|
|
cachefs_link,
|
|
cachefs_rename,
|
|
cachefs_mkdir,
|
|
cachefs_rmdir,
|
|
cachefs_readdir,
|
|
cachefs_symlink,
|
|
cachefs_readlink,
|
|
cachefs_fsync,
|
|
cachefs_inactive_queue,
|
|
cachefs_fid,
|
|
cachefs_rwlock,
|
|
cachefs_rwunlock,
|
|
cachefs_seek,
|
|
cachefs_cmp,
|
|
cachefs_frlock,
|
|
cachefs_space,
|
|
cachefs_realvp,
|
|
cachefs_getpage,
|
|
cachefs_putpage,
|
|
cachefs_map,
|
|
cachefs_addmap,
|
|
cachefs_delmap,
|
|
fs_poll,
|
|
cachefs_dump,
|
|
fs_pathconf,
|
|
cachefs_pageio,
|
|
fs_nosys,
|
|
fs_dispose,
|
|
cachefs_setsecattr,
|
|
cachefs_getsecattr
|
|
};
|
|
|
|
/* forward declarations of pure statics */
|
|
static int cachefs_readdir_back(struct cnode *, struct uio *, cred_t *, int *);
|
|
|
|
struct vnodeops *
|
|
cachefs_getvnodeops(void)
|
|
{
|
|
return (&cachefs_vnodeops);
|
|
}
|
|
|
|
static int
|
|
cachefs_open(register struct vnode **vpp, int flag, cred_t *cr)
|
|
{
|
|
int error = 0;
|
|
cnode_t *cp = VTOC(*vpp);
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_open: ENTER vpp %x flag %x \n",
|
|
(int) vpp, flag);
|
|
#endif
|
|
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if ((flag & FWRITE) &&
|
|
((*vpp)->v_type == VDIR || (*vpp)->v_type == VLNK)) {
|
|
error = EISDIR;
|
|
goto out;
|
|
}
|
|
|
|
if ((cp->c_backvp == NULL) && (flag & FWRITE)) {
|
|
error = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
if (error)
|
|
goto out;
|
|
}
|
|
if ((flag & FWRITE) && (C_ISFS_WRITE_AROUND(fscp)))
|
|
cachefs_nocache(cp);
|
|
|
|
/*
|
|
* note: open() -> vn_open() should call VOP_ACCESS() or
|
|
* VOP_CREATE() before we get here. in cachefs_access(), we
|
|
* get the backvp unless local_access is turned on.
|
|
*/
|
|
|
|
if (cp->c_backvp != NULL) {
|
|
error = VOP_OPEN(&cp->c_backvp, flag, cr);
|
|
if (error)
|
|
goto out;
|
|
}
|
|
/*
|
|
* Necessary to check for consistency here ?
|
|
*/
|
|
error = CFSOP_CHECK_COBJECT(fscp, cp, C_BACK_CHECK, RW_WRITER, cr);
|
|
out:
|
|
rw_exit(&cp->c_statelock);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_open: EXIT vpp %x error %d \n",
|
|
(int) vpp, error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
cachefs_close(struct vnode *vp, int flag, int count, offset_t offset,
|
|
cred_t *cr)
|
|
{
|
|
int error = 0;
|
|
cnode_t *cp = VTOC(vp);
|
|
struct flock ld;
|
|
vnode_t *backvp = NULL;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_close: ENTER vp %x \n", (int) vp);
|
|
#endif
|
|
|
|
rw_enter(&cp->c_statelock, RW_READER);
|
|
backvp = cp->c_backvp;
|
|
if (backvp != NULL)
|
|
VN_HOLD(backvp);
|
|
rw_exit(&cp->c_statelock);
|
|
|
|
if (MANDLOCK(vp, cp->c_attr.va_mode)) {
|
|
/*
|
|
* release all file/record locks.
|
|
*/
|
|
if (vp->v_filocks) {
|
|
ld.l_type = F_UNLCK;
|
|
ld.l_whence = 0;
|
|
ld.l_start = 0;
|
|
ld.l_len = 0;
|
|
fs_frlock(vp, F_SETLK, &ld, flag, offset, cr);
|
|
}
|
|
cleanlocks(vp, ttoproc(curthread)->p_pid, 0);
|
|
}
|
|
|
|
if (count > 1) {
|
|
if (backvp != NULL)
|
|
error = VOP_CLOSE(backvp, flag, count,
|
|
offset, cr);
|
|
goto out;
|
|
}
|
|
|
|
if (vp->v_type == VREG && backvp) {
|
|
error = VOP_FSYNC(vp, FSYNC, cr);
|
|
if (cp->c_error) {
|
|
(void) cachefs_putpage(vp, (offset_t) 0, 0,
|
|
B_INVAL | B_FORCE, cr);
|
|
(void) VOP_CLOSE(backvp, flag, count, offset, cr);
|
|
#ifdef CFSDEBUG
|
|
if (vp->v_pages) {
|
|
printf("cachefs_close: %x has pages\n",
|
|
(int) cp);
|
|
}
|
|
#endif
|
|
dnlc_purge_vp(vp);
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
error = cp->c_error;
|
|
cp->c_error = 0;
|
|
rw_exit(&cp->c_statelock);
|
|
} else {
|
|
(void) VOP_CLOSE(backvp, flag, count, offset, cr);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (backvp != NULL)
|
|
VN_RELE(backvp);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_close: EXIT vp %x \n", (int) vp);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_read(struct vnode *vp, struct uio *uiop, int ioflag, cred_t *cr)
|
|
{
|
|
struct cnode *cp = VTOC(vp);
|
|
register u_int off;
|
|
register int mapoff;
|
|
register caddr_t base;
|
|
int n;
|
|
u_int flags = 0;
|
|
int error = 0;
|
|
int type = RW_READER;
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
|
|
/*
|
|
* Do a quick check for easy cases (i.e. EOF, zero count)
|
|
*/
|
|
if (vp->v_type != VREG)
|
|
return (EISDIR);
|
|
if (uiop->uio_offset < 0)
|
|
return (EINVAL);
|
|
if ((uiop->uio_resid == 0) || (uiop->uio_offset >= cp->c_size))
|
|
return (0);
|
|
if (MANDLOCK(vp, cp->c_attr.va_mode)) {
|
|
error = chklock(vp, FREAD, uiop->uio_offset,
|
|
uiop->uio_resid, uiop->uio_fmode);
|
|
if (error)
|
|
return (error);
|
|
}
|
|
for (;;) {
|
|
rw_enter(&cp->c_statelock, type);
|
|
error = CFSOP_CHECK_COBJECT(fscp, cp, C_VERIFY_ATTRS, type, cr);
|
|
if (error != EAGAIN)
|
|
break;
|
|
rw_exit(&cp->c_statelock);
|
|
type = RW_WRITER;
|
|
}
|
|
rw_exit(&cp->c_statelock);
|
|
if (error)
|
|
return (error);
|
|
/*
|
|
* Sit in a loop and transfer (uiomove) the data in up to
|
|
* MAXBSIZE chunks. Each chunk is mapped into the kernel's
|
|
* address space as needed and then released.
|
|
*/
|
|
do {
|
|
/*
|
|
* off Offset of current MAXBSIZE chunk
|
|
* mapoff Offset within the current chunk
|
|
* n Number of bytes to move from this chunk
|
|
* base kernel address of mapped in chunk
|
|
*/
|
|
off = uiop->uio_offset & MAXBMASK;
|
|
mapoff = uiop->uio_offset & MAXBOFFSET;
|
|
n = MIN(MAXBSIZE - mapoff, uiop->uio_resid);
|
|
n = MIN((cp->c_size - uiop->uio_offset), n);
|
|
if (n == 0)
|
|
break;
|
|
base = segmap_getmap(segkmap, vp, off);
|
|
error = segmap_fault(kas.a_hat, segkmap, base, n,
|
|
F_SOFTLOCK, S_READ);
|
|
if (error) {
|
|
(void) segmap_release(segkmap, base, 0);
|
|
if (FC_CODE(error) == FC_OBJERR)
|
|
error = FC_ERRNO(error);
|
|
else
|
|
error = EIO;
|
|
break;
|
|
}
|
|
error = uiomove(base+mapoff, n, UIO_READ, uiop);
|
|
(void) segmap_fault(kas.a_hat, segkmap, base, n,
|
|
F_SOFTUNLOCK, S_READ);
|
|
if (error == 0) {
|
|
/*
|
|
* if we read a whole page(s), or to eof,
|
|
* we won't need this page(s) again soon.
|
|
*/
|
|
if (n + mapoff == MAXBSIZE ||
|
|
uiop->uio_offset == cp->c_size)
|
|
flags |= SM_DONTNEED;
|
|
}
|
|
(void) segmap_release(segkmap, base, flags);
|
|
} while (error == 0 && uiop->uio_resid > 0);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_read: EXIT error %d resid %d \n", error,
|
|
uiop->uio_resid);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_write(struct vnode *vp, struct uio *uiop, int ioflag, cred_t *cr)
|
|
{
|
|
struct cnode *cp = VTOC(vp);
|
|
int error = 0;
|
|
u_int off;
|
|
caddr_t base;
|
|
u_int flags;
|
|
int n, on;
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
rlim_t limit = uiop->uio_limit;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf(
|
|
"cachefs_write: ENTER vp %x offset %lu count %d cflags %x\n",
|
|
(int) vp, uiop->uio_offset, uiop->uio_resid,
|
|
cp->c_flags);
|
|
#endif
|
|
|
|
ASSERT(RW_WRITE_HELD(&cp->c_rwlock));
|
|
|
|
if (vp->v_type != VREG) {
|
|
error = EISDIR;
|
|
goto out;
|
|
}
|
|
if (MANDLOCK(vp, cp->c_attr.va_mode)) {
|
|
error = chklock(vp, FWRITE, uiop->uio_offset,
|
|
uiop->uio_resid, uiop->uio_fmode);
|
|
if (error)
|
|
return (error);
|
|
}
|
|
if (uiop->uio_resid == 0) {
|
|
goto out;
|
|
}
|
|
|
|
ASSERT(RW_WRITE_HELD(&cp->c_rwlock));
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (cp->c_cred != NULL)
|
|
crfree(cp->c_cred);
|
|
cp->c_cred = cr;
|
|
crhold(cr);
|
|
error = cachefs_initstate(cp, RW_WRITER, 1, cr);
|
|
if (error) {
|
|
rw_exit(&cp->c_statelock);
|
|
goto out;
|
|
}
|
|
CFSOP_CHECK_COBJECT(fscp, cp, C_VERIFY_ATTRS, RW_WRITER, cr);
|
|
if (ioflag & FAPPEND)
|
|
uiop->uio_offset = cp->c_size;
|
|
rw_exit(&cp->c_statelock);
|
|
do {
|
|
off = uiop->uio_offset & MAXBMASK; /* mapping offset */
|
|
on = uiop->uio_offset & MAXBOFFSET; /* Relative offset */
|
|
n = MIN(MAXBSIZE - on, uiop->uio_resid);
|
|
|
|
if (vp->v_type == VREG && ((uiop->uio_offset + n) >= limit)) {
|
|
if (uiop->uio_offset >= limit) {
|
|
psignal(ttoproc(curthread), SIGXFSZ);
|
|
error = EFBIG;
|
|
goto out;
|
|
}
|
|
n = limit - uiop->uio_offset;
|
|
}
|
|
|
|
base = segmap_getmap(segkmap, vp, off);
|
|
error = cachefs_writepage(vp, (base + on), n, uiop);
|
|
if (error == 0) {
|
|
flags = 0;
|
|
/*
|
|
* Have written a whole block.Start an
|
|
* asynchronous write and mark the buffer to
|
|
* indicate that it won't be needed again
|
|
* soon.
|
|
*/
|
|
if (n + on == MAXBSIZE) {
|
|
flags = SM_WRITE |SM_ASYNC |SM_DONTNEED;
|
|
}
|
|
if ((ioflag & (FSYNC|FDSYNC)) ||
|
|
(cp->c_backvp && cp->c_backvp->v_filocks)) {
|
|
flags &= ~SM_ASYNC;
|
|
flags |= SM_WRITE;
|
|
}
|
|
error = segmap_release(segkmap, base, flags);
|
|
} else {
|
|
(void) segmap_release(segkmap, base, 0);
|
|
}
|
|
} while (error == 0 && uiop->uio_resid > 0);
|
|
|
|
|
|
out:
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_write: EXIT error %d\n", error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* see if we've charged ourselves for frontfile data at
|
|
* the given offset. If not, allocate a block for it now.
|
|
*/
|
|
static int
|
|
cachefs_charge_page(struct cnode *cp, u_int offset)
|
|
{
|
|
u_int blockoff;
|
|
int error;
|
|
int inc;
|
|
|
|
ASSERT(RW_WRITE_HELD(&cp->c_statelock));
|
|
ASSERT(PAGESIZE <= MAXBSIZE);
|
|
|
|
error = 0;
|
|
blockoff = offset & MAXBMASK;
|
|
|
|
if (cachefs_check_allocmap(cp, blockoff))
|
|
return (0);
|
|
|
|
for (inc = PAGESIZE; inc < MAXBSIZE; inc += PAGESIZE)
|
|
if (cachefs_check_allocmap(cp, blockoff+inc))
|
|
return (0);
|
|
|
|
error = cachefs_allocblocks(C_TO_FSCACHE(cp)->fs_cache, 1, kcred);
|
|
if (error == 0) {
|
|
cp->c_metadata.md_frontblks++;
|
|
cp->c_flags |= CN_UPDATED;
|
|
} else {
|
|
cachefs_nocache(cp);
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_writepage(struct vnode *vp, caddr_t base, int tcount, struct uio *uiop)
|
|
/* base - base address kernel addr space */
|
|
/* tcount - Total bytes to move - < MAXBSIZE */
|
|
{
|
|
struct cnode *cp = VTOC(vp);
|
|
register int n;
|
|
register int offset;
|
|
int error = 0;
|
|
extern struct as kas;
|
|
int lastpage_off;
|
|
int pagecreate = 0;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf(
|
|
"cachefs_writepage: ENTER vp %x offset %lu len %d\\\n",
|
|
(int) vp, uiop->uio_offset, uiop->uio_resid);
|
|
#endif
|
|
|
|
/*
|
|
* Move bytes in PAGESIZE chunks. We must avoid spanning pages in
|
|
* uiomove() because page faults may cause the cache to be invalidated
|
|
* out from under us.
|
|
*/
|
|
|
|
do {
|
|
offset = uiop->uio_offset;
|
|
lastpage_off = cp->c_size & PAGEMASK;
|
|
|
|
/*
|
|
* n is the number of bytes required to satisfy the request
|
|
* or the number of bytes to fill out the page.
|
|
*/
|
|
n = MIN((PAGESIZE - ((u_int)base & PAGEOFFSET)), tcount);
|
|
|
|
/*
|
|
* Check to see if we can skip reading in the page
|
|
* and just allocate the memory. We can do this
|
|
* if we are going to rewrite the entire mapping
|
|
* or if we are going to write to or beyond the current
|
|
* end of file from the beginning of the mapping.
|
|
*/
|
|
if ((offset > (lastpage_off + PAGEOFFSET)) ||
|
|
((cp->c_size == 0) && (offset < PAGESIZE)) ||
|
|
((u_int)base & PAGEOFFSET) == 0 && (n == PAGESIZE ||
|
|
((offset + n) >= cp->c_size))) {
|
|
pagecreate = 1;
|
|
segmap_pagecreate(segkmap,
|
|
(caddr_t)((u_int)base & PAGEMASK), PAGESIZE, 0);
|
|
(void) kzero((caddr_t)((u_int)base & PAGEMASK),
|
|
(u_int)PAGESIZE);
|
|
error = uiomove(base, n, UIO_WRITE, uiop);
|
|
} else {
|
|
/*
|
|
* KLUDGE ! Use segmap_fault instead of faulting and
|
|
* using as_fault() to avoid a recursive readers lock
|
|
* on kas.
|
|
*/
|
|
error = segmap_fault(kas.a_hat, segkmap,
|
|
(caddr_t)((u_int)base & PAGEMASK),
|
|
PAGESIZE, F_SOFTLOCK, S_WRITE);
|
|
if (error) {
|
|
if (FC_CODE(error) == FC_OBJERR)
|
|
error = FC_ERRNO(error);
|
|
else
|
|
error = EIO;
|
|
break;
|
|
}
|
|
error = uiomove(base, n, UIO_WRITE, uiop);
|
|
(void) segmap_fault(kas.a_hat, segkmap,
|
|
(caddr_t)((u_int)base & PAGEMASK),
|
|
PAGESIZE, F_SOFTUNLOCK, S_WRITE);
|
|
}
|
|
n = uiop->uio_offset - offset; /* n = # of bytes written */
|
|
base += n;
|
|
tcount -= n;
|
|
/*
|
|
* cp->c_attr.va_size is the maximum number of
|
|
* bytes known to be in the file.
|
|
* Make sure it is at least as high as the
|
|
* last byte we just wrote into the buffer.
|
|
*/
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (cp->c_size < uiop->uio_offset) {
|
|
cp->c_size = uiop->uio_offset;
|
|
}
|
|
if (cp->c_size != cp->c_attr.va_size) {
|
|
cp->c_attr.va_size = cp->c_size;
|
|
cp->c_flags |= CN_UPDATED;
|
|
}
|
|
if (error == 0) {
|
|
cp->c_flags |= CDIRTY;
|
|
if (pagecreate && (cp->c_flags & CN_NOCACHE) == 0) {
|
|
/*
|
|
* if we're not in NOCACHE mode
|
|
* (i.e., single-writer), we update the
|
|
* allocmap here rather than waiting until
|
|
* cachefspush is called. This prevents
|
|
* getpage from clustering up pages from
|
|
* the backfile and stomping over the changes
|
|
* we make here.
|
|
*/
|
|
if (cachefs_charge_page(cp, offset) == 0) {
|
|
cachefs_update_allocmap(cp,
|
|
offset&PAGEMASK, PAGESIZE);
|
|
}
|
|
}
|
|
}
|
|
rw_exit(&cp->c_statelock);
|
|
} while (tcount > 0 && error == 0);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_writepage: EXIT error %d\n", error);
|
|
#endif
|
|
|
|
return (error);
|
|
}
|
|
|
|
cachefspush(struct vnode *vp, struct page *pp, u_int *offp, u_int *lenp,
|
|
u_int flags, cred_t *cr)
|
|
{
|
|
struct cnode *cp = VTOC(vp);
|
|
struct buf *bp;
|
|
int err = 0;
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
u_int iooff, iolen;
|
|
u_int lbn, lbn_off;
|
|
u_int bsize;
|
|
int blocks = 0;
|
|
int resid;
|
|
u_int popoff;
|
|
|
|
ASSERT((flags & B_ASYNC) == 0);
|
|
ASSERT(!(vp->v_vfsp->vfs_flag & VFS_RDONLY));
|
|
ASSERT(pp != NULL);
|
|
ASSERT(cr != NULL);
|
|
|
|
if (cp->c_cred != NULL)
|
|
cr = cp->c_cred;
|
|
|
|
bsize = MAXBSIZE;
|
|
lbn = pp->p_offset / bsize;
|
|
lbn_off = lbn * bsize;
|
|
|
|
/*
|
|
* Find a kluster that fits in one block, or in
|
|
* one page if pages are bigger than blocks. If
|
|
* there is less file space allocated than a whole
|
|
* page, we'll shorten the i/o request below.
|
|
*/
|
|
|
|
pp = pvn_write_kluster(vp, pp, &iooff, &iolen, lbn_off,
|
|
MAXBSIZE, flags);
|
|
|
|
if (cp->c_backvp == NULL) {
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (cp->c_backvp == NULL) {
|
|
err = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
if (err != 0) {
|
|
rw_exit(&cp->c_statelock);
|
|
err = ESTALE;
|
|
goto writedone;
|
|
}
|
|
}
|
|
rw_exit(&cp->c_statelock);
|
|
}
|
|
|
|
/*
|
|
* Set the pages up for pageout.
|
|
*/
|
|
bp = pageio_setup(pp, iolen, CTOV(cp), B_WRITE | flags);
|
|
if (bp == NULL) {
|
|
return (ENOMEM);
|
|
}
|
|
/*
|
|
* pageio_setup should have set b_addr to 0. This
|
|
* is correct since we want to do I/O on a page
|
|
* boundary. bp_mapin will use this addr to calculate
|
|
* an offset, and then set b_addr to the kernel virtual
|
|
* address it allocated for us.
|
|
*/
|
|
bp->b_edev = 0;
|
|
bp->b_dev = 0;
|
|
bp->b_blkno = btodb(iooff);
|
|
bp_mapin(bp);
|
|
|
|
/*
|
|
* If we need to write the pages out to the back file system,
|
|
* make sure we have the backvp first !
|
|
*/
|
|
iolen = MIN(bp->b_bcount, cp->c_size - dbtob(bp->b_blkno));
|
|
again:
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (C_ISFS_SINGLE(fscp) && ((cp->c_flags & CN_NOCACHE) == 0) &&
|
|
(cp->c_frontvp != NULL)) {
|
|
|
|
for (popoff = iooff; popoff < (iooff + iolen);
|
|
popoff += MAXBSIZE) {
|
|
if (cachefs_charge_page(cp, popoff) == 0) {
|
|
blocks++;
|
|
} else {
|
|
blocks = 0;
|
|
rw_exit(&cp->c_statelock);
|
|
goto again;
|
|
}
|
|
}
|
|
rw_exit(&cp->c_statelock);
|
|
ASSERT(cp->c_frontvp != NULL);
|
|
err = bp->b_error =
|
|
vn_rdwr(UIO_WRITE, cp->c_frontvp, bp->b_un.b_addr,
|
|
(int)iolen, iooff, UIO_SYSSPACE, 0,
|
|
RLIM_INFINITY, cr, &resid);
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (err) {
|
|
cachefs_nocache(cp);
|
|
err = 0;
|
|
} else {
|
|
(void) cachefs_update_allocmap(cp, iooff, iolen);
|
|
cp->c_flags |= (CN_UPDATED | CN_NEED_FRONT_SYNC |
|
|
CN_POPULATION_PENDING);
|
|
}
|
|
|
|
}
|
|
rw_exit(&cp->c_statelock);
|
|
if ((C_ISFS_WRITE_AROUND(fscp) || C_ISFS_SINGLE(fscp))) {
|
|
ASSERT(cp->c_backvp != NULL);
|
|
err = bp->b_error =
|
|
vn_rdwr(UIO_WRITE, cp->c_backvp, bp->b_un.b_addr,
|
|
(int)iolen, iooff, UIO_SYSSPACE, FSYNC,
|
|
RLIM_INFINITY, cr, &resid);
|
|
}
|
|
if (err) {
|
|
#ifdef CFSDEBUG
|
|
printf("cachefspush: error %d cr %x\n", err, (int) cr);
|
|
if (cr == kcred) {
|
|
printf("cachefspush: cred is kcred\n");
|
|
} else {
|
|
printf("cachefspush: cred is not kcred -- uid = %ld\n",
|
|
cr->cr_uid);
|
|
}
|
|
#endif
|
|
bp->b_flags |= B_ERROR;
|
|
}
|
|
bp_mapout(bp);
|
|
pageio_done(bp);
|
|
|
|
writedone:
|
|
|
|
pvn_write_done(pp, ((err) ? B_ERROR : 0) | B_WRITE | flags);
|
|
if (offp)
|
|
*offp = iooff;
|
|
if (lenp)
|
|
*lenp = iolen;
|
|
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (err) {
|
|
CFSOP_INVALIDATE_COBJECT(fscp, cp, cr);
|
|
} else {
|
|
CFSOP_MODIFY_COBJECT(fscp, cp, cr);
|
|
}
|
|
cp->c_error = err;
|
|
rw_exit(&cp->c_statelock);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_dump(struct vnode *vp, caddr_t foo1, int foo2, int foo3)
|
|
{
|
|
return (ENOSYS); /* should we panic if we get here? */
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_ioctl(struct vnode *vp, int cmd, int arg, int flag, cred_t *cred,
|
|
int *rvalp)
|
|
{
|
|
int error;
|
|
struct cnode *cp = VTOC(vp);
|
|
struct fscache *fscp = C_TO_FSCACHE(cp);
|
|
struct vnode *iovp;
|
|
struct cachefscache *cachep;
|
|
extern kmutex_t cachefs_cachelock;
|
|
extern cachefscache_t *cachefs_cachelist;
|
|
struct cachefs_cnvt_mnt ccm;
|
|
struct cachefs_boinfo cboi;
|
|
struct bootobj *bootobjp;
|
|
char *fname, *s;
|
|
int len;
|
|
|
|
switch (cmd) {
|
|
case _FIOPIN:
|
|
if (!suser(cred))
|
|
error = EPERM;
|
|
else
|
|
error = cachefs_pin(vp, cred);
|
|
break;
|
|
|
|
case _FIOUNPIN:
|
|
if (!suser(cred))
|
|
error = EPERM;
|
|
else
|
|
error = cachefs_unpin(vp, cred);
|
|
break;
|
|
|
|
case _FIOCOD:
|
|
if (!suser(cred)) {
|
|
error = EPERM;
|
|
break;
|
|
}
|
|
|
|
error = EBUSY;
|
|
if (arg) {
|
|
/* non-zero arg means do all filesystems */
|
|
mutex_enter(&cachefs_cachelock);
|
|
for (cachep = cachefs_cachelist; cachep != NULL;
|
|
cachep = cachep->c_next) {
|
|
mutex_enter(&cachep->c_fslistlock);
|
|
for (fscp = cachep->c_fslist;
|
|
fscp != NULL;
|
|
fscp = fscp->fs_next) {
|
|
if (C_ISFS_CODCONST(fscp)) {
|
|
fscp->fs_cod_time =
|
|
hrestime;
|
|
error = 0;
|
|
}
|
|
}
|
|
mutex_exit(&cachep->c_fslistlock);
|
|
}
|
|
mutex_exit(&cachefs_cachelock);
|
|
} else {
|
|
if (C_ISFS_CODCONST(fscp)) {
|
|
fscp->fs_cod_time = hrestime;
|
|
error = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case _FIOCNVTMNT:
|
|
if (!suser(cred)) {
|
|
error = EPERM;
|
|
break;
|
|
}
|
|
|
|
/* Now copyin the convert structure */
|
|
error = copyin((caddr_t)arg, (caddr_t)&ccm,
|
|
sizeof (struct cachefs_cnvt_mnt));
|
|
|
|
if (error)
|
|
break;
|
|
|
|
if (ccm.cm_op != CFS_CM_BACK && ccm.cm_op != CFS_CM_FRONT) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (ccm.cm_namelen == 0) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
/* Now copyin the name */
|
|
fname = (caddr_t)cachefs_kmem_alloc(ccm.cm_namelen, KM_SLEEP);
|
|
if (copyin(ccm.cm_name, fname, ccm.cm_namelen)) {
|
|
cachefs_kmem_free(fname, ccm.cm_namelen);
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
ccm.cm_name = fname;
|
|
error = cachefs_convert_mount(fscp, &ccm);
|
|
cachefs_kmem_free(fname, ccm.cm_namelen);
|
|
break;
|
|
|
|
case _FIOBOINFO:
|
|
if (!suser(cred)) {
|
|
error = EPERM;
|
|
break;
|
|
}
|
|
|
|
/* Now copyin the bootobj info structure */
|
|
error = copyin((caddr_t)arg, (caddr_t)&cboi,
|
|
sizeof (struct cachefs_boinfo));
|
|
if (error)
|
|
break;
|
|
|
|
/* get the piece we're interested in and copy it back out */
|
|
error = 0;
|
|
switch (cboi.boi_which) {
|
|
case CFS_BOI_ROOTFS:
|
|
bootobjp = &rootfs;
|
|
break;
|
|
case CFS_BOI_FRONTFS:
|
|
bootobjp = &frontfs;
|
|
break;
|
|
case CFS_BOI_BACKFS:
|
|
bootobjp = &backfs;
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
if (error)
|
|
break;
|
|
|
|
s = cboi.boi_device ? bootobjp->bo_name : bootobjp->bo_fstype;
|
|
len = strlen(s) + 1;
|
|
len = MIN(len, cboi.boi_len);
|
|
error = copyout((caddr_t)s, (caddr_t)cboi.boi_value, len);
|
|
break;
|
|
|
|
default:
|
|
if (cp->c_backvp == NULL) {
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
error = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
rw_exit(&cp->c_statelock);
|
|
if (error)
|
|
break;
|
|
}
|
|
iovp = cp->c_backvp;
|
|
error = VOP_IOCTL(iovp, cmd, arg, flag, cred, rvalp);
|
|
}
|
|
|
|
/* return the result */
|
|
return (error);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_getattr(struct vnode *vp, struct vattr *vap, int flags, cred_t *cr)
|
|
{
|
|
struct cnode *cp = VTOC(vp);
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
int error = EAGAIN;
|
|
int type = RW_READER;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_getattr: ENTER vp %x\n", (int) vp);
|
|
#endif
|
|
|
|
#if MAXFIDSZ < 32
|
|
panic("FID SIZE IS WRONG COMPILE WITH NEW vfs.h");
|
|
#endif
|
|
|
|
while (error == EAGAIN) {
|
|
rw_enter(&cp->c_statelock, type);
|
|
error = CFSOP_CHECK_COBJECT(fscp, cp, C_VERIFY_ATTRS, type, cr);
|
|
if (error != EAGAIN)
|
|
break;
|
|
rw_exit(&cp->c_statelock);
|
|
type = RW_WRITER;
|
|
}
|
|
if (error == 0) {
|
|
*vap = cp->c_attr;
|
|
if (cp->c_size > vap->va_size)
|
|
vap->va_size = cp->c_size;
|
|
}
|
|
rw_exit(&cp->c_statelock);
|
|
vap->va_fsid = vp->v_vfsp->vfs_dev;
|
|
|
|
out:
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_getattr: EXIT error = %d\n", error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_setattr(register struct vnode *vp, register struct vattr *vap,
|
|
int flags, cred_t *cr)
|
|
{
|
|
cnode_t *cp = VTOC(vp);
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
register long int mask = vap->va_mask;
|
|
int error = 0;
|
|
u_int bcnt;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_setattr: ENTER vp %x\n", (int) vp);
|
|
#endif
|
|
/*
|
|
* Cannot set these attributes.
|
|
*/
|
|
if (mask & AT_NOSET)
|
|
return (EINVAL);
|
|
/*
|
|
* Truncate file. Must have write permission and not be a directory.
|
|
*/
|
|
if (mask & AT_SIZE) {
|
|
if (vp->v_type == VDIR) {
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_TRUNCATE))
|
|
cachefs_log_truncate(cachep, EISDIR,
|
|
fscp->fs_cfsvfsp,
|
|
&cp->c_metadata.md_cookie, cp->c_fileno,
|
|
cr->cr_uid, vap->va_size);
|
|
return (EISDIR);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Gotta deal with one special case here, where we're setting the
|
|
* size of the file. First, we zero out part of the page after the
|
|
* new size of the file. Then we toss (not write) all pages after
|
|
* page in which the new offset occurs. Note that the NULL passed
|
|
* in instead of a putapage() fn parameter is correct, since
|
|
* no dirty pages will be found (B_TRUNC | B_INVAL).
|
|
*/
|
|
rw_enter(&cp->c_rwlock, RW_WRITER);
|
|
|
|
/* sync dirty pages */
|
|
(void) cachefs_putpage(vp, (offset_t)0, 0, 0, cr);
|
|
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (cp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp, &cp->c_metadata.md_cookie, cp);
|
|
if (error) {
|
|
rw_exit(&cp->c_statelock);
|
|
goto out;
|
|
}
|
|
}
|
|
error = VOP_SETATTR(cp->c_backvp, vap, flags, cr);
|
|
if (error) {
|
|
rw_exit(&cp->c_statelock);
|
|
goto out;
|
|
}
|
|
if (mask & AT_SIZE) {
|
|
cp->c_size = vap->va_size;
|
|
cachefs_inval_object(cp, cr);
|
|
}
|
|
cp->c_flags |= CN_UPDATED;
|
|
cp->c_attr.va_mask = AT_ALL;
|
|
error = VOP_GETATTR(cp->c_backvp, &cp->c_attr, 0, cr);
|
|
if (error) {
|
|
rw_exit(&cp->c_statelock);
|
|
goto out;
|
|
}
|
|
/* ASSERT(cp->c_fileno == cp->c_attr.va_nodeid); */
|
|
cp->c_attr.va_size = MAX(cp->c_attr.va_size, cp->c_size);
|
|
cp->c_size = cp->c_attr.va_size;
|
|
/*
|
|
* Attr's have changed, so we need to call the CFSOP_MODIFY_COBJECT
|
|
* function to notify the consistency interface of this.
|
|
*/
|
|
CFSOP_MODIFY_COBJECT(fscp, cp, cr);
|
|
rw_exit(&cp->c_statelock);
|
|
|
|
/*
|
|
* If the file size has been changed then
|
|
* toss whole pages beyond the end of the file and zero
|
|
* the portion of the last page that is beyond the end of the file.
|
|
*/
|
|
if (mask & AT_SIZE) {
|
|
bcnt = cp->c_size & PAGEOFFSET;
|
|
if (bcnt)
|
|
pvn_vpzero(vp, cp->c_size, PAGESIZE - bcnt);
|
|
pvn_vplist_dirty(vp, cp->c_size, cachefspush,
|
|
B_TRUNC | B_INVAL, cr);
|
|
}
|
|
|
|
out:
|
|
rw_exit(&cp->c_rwlock);
|
|
|
|
if ((mask & AT_SIZE) &&
|
|
(CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_TRUNCATE)))
|
|
cachefs_log_truncate(cachep, error, fscp->fs_cfsvfsp,
|
|
&cp->c_metadata.md_cookie, cp->c_fileno, cr->cr_uid,
|
|
vap->va_size);
|
|
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_setattr: EXIT vp %x error %d \n",
|
|
(int) vp, error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
cachefsaccess(cnode_t *cp, fscache_t *fscp, int mode, int type, cred_t *cr)
|
|
{
|
|
struct vattr *vap = &cp->c_attr;
|
|
int error = 0;
|
|
|
|
/*
|
|
* Make sure the cnode attrs are valid first.
|
|
*/
|
|
error = CFSOP_CHECK_COBJECT(fscp, cp, C_VERIFY_ATTRS, type, cr);
|
|
if (error)
|
|
goto out;
|
|
if ((fscp->fs_options.opt_flags & CFS_ACCESS_BACKFS) == 0) {
|
|
/*
|
|
* Disallow write attempt on read-only
|
|
* file systems, except for device/special files
|
|
*/
|
|
if (mode & VWRITE) {
|
|
struct vnode *vp = CTOV(cp);
|
|
if (vp->v_vfsp->vfs_flag & VFS_RDONLY) {
|
|
if (!C_ISVDEV(vp->v_type)) {
|
|
return (EROFS);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If you're the super-user,
|
|
* you always get access.
|
|
*/
|
|
if (cr->cr_uid == 0)
|
|
return (0);
|
|
/*
|
|
* Access check is based on only
|
|
* one of owner, group, public.
|
|
* If not owner, then check group.
|
|
* If not a member of the group,
|
|
* then check public access.
|
|
*
|
|
*/
|
|
if (cr->cr_uid != vap->va_uid) {
|
|
mode >>= 3;
|
|
if (!groupmember(vap->va_gid, cr))
|
|
mode >>= 3;
|
|
}
|
|
if ((vap->va_mode & mode) == mode)
|
|
error = 0;
|
|
else
|
|
error = EACCES;
|
|
|
|
} else {
|
|
if (cp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
if (error)
|
|
goto out;
|
|
}
|
|
ASSERT(cp->c_backvp != NULL);
|
|
error = VOP_ACCESS(cp->c_backvp, mode, 0, cr);
|
|
}
|
|
out:
|
|
return (error);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
cachefs_access(struct vnode *vp, int mode, int flags, cred_t *cr)
|
|
{
|
|
cnode_t *cp = VTOC(vp);
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
int error = 0;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_access: ENTER vp %x\n", (int) vp);
|
|
#endif
|
|
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if ((fscp->fs_options.opt_flags & CFS_ACCESS_BACKFS) != 0)
|
|
error = cachefs_initstate(cp, RW_WRITER, 0, cr);
|
|
if (!error)
|
|
error = cachefsaccess(cp, fscp, mode, RW_WRITER, cr);
|
|
rw_exit(&cp->c_statelock);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_access: EXIT error = %d\n", error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* CFS has a fastsymlink scheme. If the size of the link is < C_FSL_SIZE, then
|
|
* the link is placed in the metadata itself (no front file is allocated).
|
|
*/
|
|
static int
|
|
cachefs_readlink(struct vnode *vp, struct uio *uiop, cred_t *cr)
|
|
{
|
|
int error = 0;
|
|
cnode_t *cp = VTOC(vp);
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
int readcache = 0;
|
|
int len = 0;
|
|
|
|
if (vp->v_type != VLNK) /* XXX don't bother logging? */
|
|
return (ENXIO);
|
|
|
|
readcache = 1;
|
|
rw_enter(&cp->c_rwlock, RW_WRITER);
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if ((cp->c_metadata.md_flags & (MD_POPULATED|MD_FASTSYMLNK)) == 0) {
|
|
/*
|
|
* symlink data is not cached yet.
|
|
* First, make sure we have a backvp.
|
|
*/
|
|
if (cp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
if (error)
|
|
goto out;
|
|
}
|
|
/*
|
|
* If possible, call cachefs_cachesymlink() to cache the
|
|
* data -- either as a fast symlink or in a frontfile.
|
|
*/
|
|
if ((cp->c_flags & CN_NOCACHE) == 0 &&
|
|
(fscp->fs_flags & CFS_FS_WRITE)) {
|
|
error = cachefs_cachesymlink(cp, cr);
|
|
if (error) {
|
|
readcache = 0;
|
|
cachefs_nocache(cp);
|
|
error = 0;
|
|
}
|
|
} else {
|
|
readcache = 0;
|
|
}
|
|
} else if (cp->c_metadata.md_flags & MD_POPULATED) {
|
|
/*
|
|
* symlink data is cached in a file (as opposed to in
|
|
* the metadata as a "fast symlink"). Make sure
|
|
* we've got the vp for the file.
|
|
*/
|
|
ASSERT(cp->c_metadata.md_flags & MD_FILE);
|
|
if (cp->c_frontvp == NULL) {
|
|
error = cachefs_getfrontfile(cp,
|
|
(struct vattr *)NULL, cr);
|
|
if (error) {
|
|
readcache = 0;
|
|
cachefs_nocache(cp);
|
|
error = 0;
|
|
}
|
|
}
|
|
}
|
|
rw_downgrade(&cp->c_statelock);
|
|
rw_downgrade(&cp->c_rwlock);
|
|
|
|
if (readcache == 0) {
|
|
ASSERT(cp->c_backvp != NULL);
|
|
error = VOP_READLINK(cp->c_backvp, uiop, cr);
|
|
len = uiop->uio_resid;
|
|
} else if (cp->c_metadata.md_flags & MD_FASTSYMLNK) {
|
|
error = uiomove((caddr_t)cp->c_metadata.md_allocinfo,
|
|
len = (int)MIN(cp->c_size, uiop->uio_resid),
|
|
UIO_READ, uiop);
|
|
} else {
|
|
ASSERT(cp->c_frontvp != NULL);
|
|
ASSERT((cp->c_metadata.md_flags & (MD_FILE|MD_POPULATED)) ==
|
|
(MD_FILE|MD_POPULATED));
|
|
/*
|
|
* Read symlink data from frontfile
|
|
*/
|
|
uiop->uio_offset = 0;
|
|
VOP_RWLOCK(cp->c_frontvp, 0);
|
|
error = VOP_READ(cp->c_frontvp, uiop, 0, cr);
|
|
VOP_RWUNLOCK(cp->c_frontvp, 0);
|
|
len = uiop->uio_resid;
|
|
}
|
|
out:
|
|
if (error == 0) {
|
|
if (readcache)
|
|
fscp->fs_stats.st_hits++;
|
|
else
|
|
fscp->fs_stats.st_misses++;
|
|
}
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_READLINK))
|
|
cachefs_log_readlink(cachep, error, fscp->fs_cfsvfsp,
|
|
&cp->c_metadata.md_cookie, cp->c_fileno, cr->cr_uid, len);
|
|
|
|
rw_exit(&cp->c_statelock);
|
|
rw_exit(&cp->c_rwlock);
|
|
return (error);
|
|
}
|
|
|
|
int
|
|
cachefs_fsync(struct vnode *vp, int syncflag, cred_t *cr)
|
|
{
|
|
struct cnode *cp = VTOC(vp);
|
|
int error = 0;
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_fsync: ENTER vp %x \n", (int) vp);
|
|
#endif
|
|
|
|
if (fscp->fs_backvfsp->vfs_flag & VFS_RDONLY)
|
|
goto out;
|
|
|
|
/*
|
|
* Clean the cachefs pages synchronously
|
|
*/
|
|
error = cachefs_putpage(vp, (offset_t)0, 0, 0, cr);
|
|
if (error)
|
|
goto out;
|
|
if (C_ISFS_WRITE_AROUND(fscp)) {
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (cp->c_backvp == NULL) {
|
|
/*
|
|
* believe it or not, this can happen.
|
|
* The SVVS suite has a test that hits
|
|
* this case for a FIFO
|
|
*/
|
|
error = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
}
|
|
if (error == 0) {
|
|
error = VOP_FSYNC(cp->c_backvp, syncflag, cr);
|
|
if (error)
|
|
cp->c_error = error;
|
|
}
|
|
if (error == 0)
|
|
cp->c_flags &= ~CDIRTY;
|
|
rw_exit(&cp->c_statelock);
|
|
}
|
|
out:
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_fsync: EXIT vp %x \n", (int) vp);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Called from cachefs_inactive(), to make sure all the data goes out to disk.
|
|
*/
|
|
int
|
|
cachefs_sync_metadata(cnode_t *cp, cred_t *cr)
|
|
{
|
|
int error = 0;
|
|
struct filegrp *fgp;
|
|
struct vattr va;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("c_sync_metadata: ENTER cp %x cflag %x\n",
|
|
(int) cp, cp->c_flags);
|
|
#endif
|
|
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if ((cp->c_flags & CN_UPDATED) == 0)
|
|
goto out;
|
|
if (cp->c_flags & CN_STALE)
|
|
goto out;
|
|
fgp = cp->c_filegrp;
|
|
if ((fgp->fg_flags & CFS_FG_WRITE) == 0)
|
|
goto out;
|
|
|
|
if (fgp->fg_flags & CFS_FG_ALLOC_ATTR) {
|
|
rw_exit(&cp->c_statelock);
|
|
error = filegrp_allocattr(fgp);
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (error) {
|
|
error = 0;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (cp->c_flags & CN_ALLOC_PENDING) {
|
|
error = filegrp_create_metadata(fgp, &cp->c_metadata,
|
|
cp->c_fileno);
|
|
if (error)
|
|
goto out;
|
|
cp->c_flags &= ~CN_ALLOC_PENDING;
|
|
}
|
|
|
|
if (cp->c_flags & CN_NEED_FRONT_SYNC) {
|
|
if (cp->c_frontvp != NULL) {
|
|
error = VOP_FSYNC(cp->c_frontvp, FSYNC, cr);
|
|
if (error) {
|
|
cp->c_metadata.md_timestamp.tv_sec = 0;
|
|
} else {
|
|
va.va_mask = AT_MTIME;
|
|
error = VOP_GETATTR(cp->c_frontvp, &va, 0, cr);
|
|
if (error)
|
|
goto out;
|
|
cp->c_metadata.md_timestamp = va.va_mtime;
|
|
cp->c_flags &=
|
|
~(CN_NEED_FRONT_SYNC | CN_POPULATION_PENDING);
|
|
}
|
|
} else {
|
|
cp->c_flags &=
|
|
~(CN_NEED_FRONT_SYNC | CN_POPULATION_PENDING);
|
|
}
|
|
}
|
|
|
|
if ((cp->c_flags & CN_ALLOC_PENDING) == 0 &&
|
|
(cp->c_flags & CN_UPDATED)) {
|
|
error = filegrp_write_metadata(fgp, cp->c_fileno,
|
|
&cp->c_metadata);
|
|
if (error)
|
|
goto out;
|
|
}
|
|
out:
|
|
if (error) {
|
|
ASSERT((cp->c_flags & CN_LRU) == 0);
|
|
if (cp->c_metadata.md_lruno) {
|
|
cachefs_removefrontfile(&cp->c_metadata,
|
|
cp->c_fileno, fgp);
|
|
cachefs_active_remove(C_TO_FSCACHE(cp)->fs_cache,
|
|
cp->c_metadata.md_lruno);
|
|
cachefs_lru_free(C_TO_FSCACHE(cp)->fs_cache,
|
|
cp->c_metadata.md_lruno);
|
|
cp->c_metadata.md_lruno = 0;
|
|
if (cp->c_frontvp) {
|
|
VN_RELE(cp->c_frontvp);
|
|
cp->c_frontvp = NULL;
|
|
}
|
|
}
|
|
(void) filegrp_destroy_metadata(fgp, cp->c_fileno);
|
|
cp->c_flags |= CN_ALLOC_PENDING;
|
|
cachefs_nocache(cp);
|
|
}
|
|
/*
|
|
* we clear the updated bit even on errors because a retry
|
|
* will probably fail also.
|
|
*/
|
|
cp->c_flags &= ~CN_UPDATED;
|
|
rw_exit(&cp->c_statelock);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("c_sync_metadata: EXIT cp %x cflag %x\n",
|
|
(int) cp, cp->c_flags);
|
|
#endif
|
|
|
|
return (error);
|
|
}
|
|
|
|
extern int maxcnodes;
|
|
|
|
/*
|
|
* This is the vop entry point for inactivating a vnode.
|
|
* It just queues the request for the async thread which
|
|
* calls cachefs_inactive.
|
|
*/
|
|
static void
|
|
cachefs_inactive_queue(register struct vnode *vp, cred_t *cr)
|
|
{
|
|
cnode_t *cp;
|
|
struct cachefs_req *rp;
|
|
struct vnode *backvp;
|
|
int error;
|
|
struct cachefscache *cachep;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_inactive_queue: ENTER vp %x \n", (int) vp);
|
|
#endif
|
|
|
|
cp = VTOC(vp);
|
|
|
|
ASSERT((cp->c_flags & CN_INACTIVE) == 0);
|
|
|
|
/* vn_rele() set the v_count == 1 */
|
|
|
|
if ((cp->c_flags & CN_DESTROY) && (cp->c_backvp)) {
|
|
if (vp->v_pages) {
|
|
(void) pvn_vplist_dirty(vp, (u_int) 0,
|
|
(int (*)())NULL, B_INVAL|B_TRUNC, cr);
|
|
}
|
|
if (vp->v_count == 1) {
|
|
backvp = cp->c_backvp;
|
|
cp->c_backvp = NULL;
|
|
VN_RELE(backvp);
|
|
}
|
|
}
|
|
|
|
cp->c_ipending = 1;
|
|
|
|
rp = (struct cachefs_req *)cachefs_kmem_zalloc(
|
|
/*LINTED alignment okay*/
|
|
sizeof (struct cachefs_req), KM_SLEEP);
|
|
mutex_init(&rp->cfs_req_lock, "CFS Request Mutex", MUTEX_DEFAULT, NULL);
|
|
rp->cfs_cmd = CFS_INACTIVE;
|
|
rp->cfs_cr = cr;
|
|
crhold(rp->cfs_cr);
|
|
rp->cfs_req_u.cu_inactive.ci_vp = vp;
|
|
error = cachefs_addqueue(rp, &(C_TO_FSCACHE(cp)->fs_workq));
|
|
if (error) {
|
|
cachep = C_TO_FSCACHE(cp)->fs_cache;
|
|
error = cachefs_addqueue(rp, &cachep->c_workq);
|
|
ASSERT(error == 0);
|
|
}
|
|
|
|
out:; /* semicolon so we have something before close-brace ifndef CFSDEBUG */
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_inactive_queue: EXIT vp %x \n", (int) vp);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* This routine does the real work of inactivating a vnode.
|
|
*/
|
|
void
|
|
cachefs_inactive(register struct vnode *vp, cred_t *cr)
|
|
{
|
|
cnode_t *cp;
|
|
struct fscache *fscp;
|
|
int error;
|
|
int decvnoderef = 0;
|
|
struct filegrp *fgp;
|
|
cachefscache_t *cachep;
|
|
struct cachefs_metadata *mdp;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_inactive: ENTER vp %x \n", (int) vp);
|
|
#endif
|
|
|
|
cp = VTOC(vp);
|
|
ASSERT((cp->c_flags & CN_INACTIVE) == 0);
|
|
|
|
fscp = C_TO_FSCACHE(cp);
|
|
cachep = fscp->fs_cache;
|
|
ASSERT(cachep != NULL);
|
|
fgp = cp->c_filegrp;
|
|
mdp = &cp->c_metadata;
|
|
|
|
if ((cp->c_flags & CN_NOCACHE) && (cp->c_metadata.md_flags & MD_FILE)) {
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
cachefs_inval_object(cp, cr);
|
|
rw_exit(&cp->c_statelock);
|
|
}
|
|
|
|
/* lock the file group against action by lookup or gc */
|
|
filegrp_hold(fgp);
|
|
mutex_enter(&fgp->fg_gc_mutex);
|
|
|
|
for (;;) {
|
|
/* see if vnode is really inactive */
|
|
mutex_enter(&vp->v_lock);
|
|
ASSERT(vp->v_count > 0);
|
|
if (vp->v_count > 1) {
|
|
cp->c_ipending = 0;
|
|
vp->v_count--; /* release our hold from vn_rele */
|
|
mutex_exit(&vp->v_lock);
|
|
goto out;
|
|
}
|
|
mutex_exit(&vp->v_lock);
|
|
|
|
error = 0;
|
|
if (vp->v_pages) {
|
|
error = cachefs_putpage(vp, (offset_t)0, 0, B_FREE, cr);
|
|
if (vp->v_pages && cp->c_error) {
|
|
(void) cachefs_putpage(vp, (offset_t) 0, 0,
|
|
B_INVAL | B_FORCE, cr);
|
|
if (vp->v_pages) {
|
|
printf("inactive: cp %x still"
|
|
" has pages\n", (int) cp);
|
|
error = ESTALE;
|
|
} else {
|
|
error = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* see if vnode is really inactive */
|
|
mutex_enter(&vp->v_lock);
|
|
ASSERT(vp->v_count > 0);
|
|
if (error || (vp->v_count > 1)) {
|
|
cp->c_ipending = 0;
|
|
vp->v_count--; /* release our hold from vn_rele */
|
|
mutex_exit(&vp->v_lock);
|
|
#ifdef CFSDEBUG
|
|
if (error) {
|
|
printf("cachefs_inactive: putpage error %d "
|
|
"vp %x\n", error, (int) vp);
|
|
}
|
|
#endif
|
|
goto out;
|
|
}
|
|
mutex_exit(&vp->v_lock);
|
|
|
|
/* if need to sync metadata */
|
|
if ((cp->c_flags & (CN_UPDATED | CN_DESTROY)) == CN_UPDATED) {
|
|
(void) cachefs_sync_metadata(cp, cr);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* if we need to get rid of this cnode */
|
|
if ((cp->c_flags & (CN_DESTROY | CN_STALE)) ||
|
|
(cachefs_cnode_cnt(0) > maxcnodes)) {
|
|
/* remove the cnode from the hash list */
|
|
mutex_enter(&fscp->fs_cnodelock);
|
|
cachefs_remhash(cp);
|
|
mutex_exit(&fscp->fs_cnodelock);
|
|
|
|
/* get rid of any pages */
|
|
(void) cachefs_putpage(vp, (offset_t)0, 0, B_INVAL, cr);
|
|
if (vp->v_pages)
|
|
(void) cachefs_putpage(vp, (offset_t)0, 0,
|
|
B_TRUNC | B_INVAL, cr);
|
|
ASSERT(vp->v_pages == NULL);
|
|
ASSERT(vp->v_count == 1);
|
|
|
|
if (cp->c_cred != NULL) {
|
|
crfree(cp->c_cred);
|
|
cp->c_cred = NULL;
|
|
}
|
|
|
|
/* if we can (and should) destory the front file and metadata */
|
|
if (((cp->c_flags & (CN_DESTROY | CN_STALE)) == CN_DESTROY) &&
|
|
(fgp->fg_flags & CFS_FG_WRITE)) {
|
|
if (mdp->md_lruno) {
|
|
cachefs_removefrontfile(mdp, cp->c_fileno, fgp);
|
|
if (cp->c_flags & CN_LRU) {
|
|
cachefs_lru_remove(cachep,
|
|
mdp->md_lruno);
|
|
} else {
|
|
cachefs_active_remove(cachep,
|
|
mdp->md_lruno);
|
|
}
|
|
cachefs_lru_free(cachep, mdp->md_lruno);
|
|
}
|
|
(void) filegrp_destroy_metadata(fgp, cp->c_fileno);
|
|
}
|
|
|
|
/* else make sure the front file is on the lru list */
|
|
else if (mdp->md_lruno &&
|
|
((mdp->md_flags & MD_PINNED) == 0) &&
|
|
((cp->c_flags & CN_LRU) == 0)) {
|
|
ASSERT((cachep->c_flags & CACHE_NOFILL) == 0);
|
|
cachefs_active_remove(cachep, mdp->md_lruno);
|
|
cachefs_lru_add(cachep, mdp->md_lruno);
|
|
}
|
|
|
|
if (cp->c_frontvp)
|
|
VN_RELE(cp->c_frontvp);
|
|
|
|
if (cp->c_backvp)
|
|
VN_RELE(cp->c_backvp);
|
|
|
|
bzero((caddr_t)cp, sizeof (cnode_t));
|
|
cachefs_kmem_free((caddr_t)cp, sizeof (cnode_t));
|
|
filegrp_rele(fgp);
|
|
(void) cachefs_cnode_cnt(-1);
|
|
decvnoderef++;
|
|
} else {
|
|
/* else make it inactive and put it on the cnode free list */
|
|
/*
|
|
* Get anyone (like sync) that looks at the cnode hash
|
|
* list to ignore this cnode.
|
|
*/
|
|
mutex_enter(&fscp->fs_cnodelock);
|
|
cp->c_flags |= CN_HASHSKIP;
|
|
mutex_exit(&fscp->fs_cnodelock);
|
|
|
|
/* release our "hold" from vn_rele */
|
|
mutex_enter(&vp->v_lock);
|
|
ASSERT(vp->v_count == 1);
|
|
decvnoderef++;
|
|
vp->v_count--;
|
|
mutex_exit(&vp->v_lock);
|
|
|
|
/* put on lru list if necessary */
|
|
if (mdp->md_lruno &&
|
|
((cachep->c_flags & CACHE_NOFILL) == 0) &&
|
|
((mdp->md_flags & MD_PINNED) == 0)) {
|
|
if (cp->c_flags & CN_LRU) {
|
|
cachefs_lru_remove(cachep, mdp->md_lruno);
|
|
} else {
|
|
cachefs_active_remove(cachep, mdp->md_lruno);
|
|
}
|
|
cachefs_lru_add(cachep, mdp->md_lruno);
|
|
cp->c_flags |= CN_LRU;
|
|
}
|
|
|
|
/* release front and back files */
|
|
if (cp->c_frontvp) {
|
|
VN_RELE(cp->c_frontvp);
|
|
cp->c_frontvp = NULL;
|
|
}
|
|
if (cp->c_backvp) {
|
|
VN_RELE(cp->c_backvp);
|
|
cp->c_backvp = NULL;
|
|
}
|
|
if (cp->c_cred != NULL) {
|
|
crfree(cp->c_cred);
|
|
cp->c_cred = NULL;
|
|
}
|
|
cp->c_error = 0;
|
|
|
|
/*
|
|
* leave ALLOC_PENDING alone here. It could be ON
|
|
* when we are in NOCACHE mode. Then we need to have
|
|
* ALLOC_PENDING on so that the right things happen
|
|
* later when a remount enables caching
|
|
*/
|
|
|
|
if ((cachep->c_flags & CACHE_NOCACHE) == 0)
|
|
cp->c_flags &= ~CN_NOCACHE;
|
|
|
|
mutex_enter(&fscp->fs_cnodelock);
|
|
cp->c_flags &= ~CN_HASHSKIP;
|
|
mutex_exit(&fscp->fs_cnodelock);
|
|
|
|
/*
|
|
* Put on free list and mark as inactive.
|
|
*/
|
|
mutex_enter(&cachefs_cnode_freelist_lock);
|
|
cachefs_addfree(cp);
|
|
cp->c_flags |= CN_INACTIVE;
|
|
cp->c_filegrp = NULL;
|
|
ASSERT((cp->c_flags & CN_UPDATED) == 0);
|
|
mutex_exit(&cachefs_cnode_freelist_lock);
|
|
|
|
filegrp_rele(fgp);
|
|
}
|
|
out:
|
|
mutex_exit(&fgp->fg_gc_mutex);
|
|
filegrp_rele(fgp);
|
|
|
|
if (decvnoderef) {
|
|
fscache_rele(fscp);
|
|
}
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_inactive: EXIT vp %x \n", (int) vp);
|
|
#endif
|
|
}
|
|
|
|
int cachefs_dnlc = 1; /* use dnlc */
|
|
|
|
int
|
|
cachefs_initstate(cnode_t *cp, int type, int alloc, cred_t *cr)
|
|
{
|
|
int error = 0;
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
|
|
ASSERT(type == RW_READER || type == RW_WRITER);
|
|
if ((cp->c_frontvp || (cp->c_flags & CN_NOCACHE)) && cp->c_backvp)
|
|
return (0);
|
|
if (type != RW_WRITER)
|
|
if (rw_tryupgrade(&cp->c_statelock) != 1)
|
|
return (EAGAIN);
|
|
if ((cp->c_flags & CN_NOCACHE) == 0 && cp->c_frontvp == NULL &&
|
|
((cp->c_metadata.md_flags & MD_FILE) || alloc)) {
|
|
error = cachefs_getfrontfile(cp, (struct vattr *)NULL, cr);
|
|
if (error) {
|
|
cachefs_nocache(cp);
|
|
error = 0;
|
|
}
|
|
}
|
|
if (cp->c_backvp == NULL)
|
|
error = cachefs_getbackvp(fscp, &cp->c_metadata.md_cookie, cp);
|
|
if (type == RW_READER)
|
|
rw_downgrade(&cp->c_statelock);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Remote file system operations having to do with directory manipulation.
|
|
*/
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
cachefs_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
|
|
struct pathname *pnp, int flags, struct vnode *rdir, cred_t *cr)
|
|
{
|
|
int error = 0;
|
|
cnode_t *cp, *dcp = VTOC(dvp);
|
|
fscache_t *fscp = C_TO_FSCACHE(dcp);
|
|
struct fid cookie;
|
|
u_int d_offset;
|
|
ino_t fileno;
|
|
u_int flag;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_lookup: ENTER dvp %x nm %s\n", (int) dvp, nm);
|
|
#endif
|
|
|
|
/*
|
|
* If lookup is for ".", just return dvp. Don't need
|
|
* to send it over the wire or look it up in the dnlc.
|
|
* Null component is a synonym for current directory.
|
|
*/
|
|
if (strcmp(nm, ".") == 0 || *nm == '\0') {
|
|
VN_HOLD(dvp);
|
|
*vpp = dvp;
|
|
goto out1;
|
|
}
|
|
/* VOP_ACCESS will verify consistency for us */
|
|
error = VOP_ACCESS(dvp, VEXEC, 0, cr);
|
|
if (error)
|
|
goto out1;
|
|
/*
|
|
* Read lock the cnode before starting the search.
|
|
*/
|
|
rw_enter(&dcp->c_rwlock, RW_READER);
|
|
|
|
*vpp = (vnode_t *)dnlc_lookup(dvp, nm, cr);
|
|
if (*vpp)
|
|
goto out;
|
|
/*
|
|
* Didn't get a dnlc hit. We have to search the directory.
|
|
*/
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
if ((dcp->c_flags & CN_NOCACHE) == 0 && dcp->c_frontvp == NULL) {
|
|
error = cachefs_getfrontfile(dcp, (struct vattr *)NULL, cr);
|
|
if (error) {
|
|
cachefs_nocache(dcp);
|
|
rw_exit(&dcp->c_statelock);
|
|
error = cachefs_lookup_back(dvp, nm, vpp, 0, cr);
|
|
goto out;
|
|
}
|
|
ASSERT(dcp->c_frontvp != NULL);
|
|
}
|
|
dcp->c_usage++;
|
|
if ((dcp->c_flags & CN_NOCACHE) ||
|
|
((dcp->c_filegrp->fg_flags & CFS_FG_READ) == 0)) {
|
|
rw_exit(&dcp->c_statelock);
|
|
error = cachefs_lookup_back(dvp, nm, vpp, 0, cr);
|
|
goto out;
|
|
}
|
|
|
|
if ((dcp->c_metadata.md_flags & MD_POPULATED) == 0) {
|
|
if ((dcp->c_filegrp->fg_flags & CFS_FG_WRITE) == 0) {
|
|
rw_exit(&dcp->c_statelock);
|
|
error = cachefs_lookup_back(dvp, nm, vpp, 0, cr);
|
|
goto out;
|
|
}
|
|
if (dcp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp,
|
|
&dcp->c_metadata.md_cookie, dcp);
|
|
if (error) {
|
|
rw_exit(&dcp->c_statelock);
|
|
goto out;
|
|
}
|
|
}
|
|
error = cachefs_filldir(dcp, RW_WRITER, cr);
|
|
if (error) {
|
|
rw_exit(&dcp->c_statelock);
|
|
error = cachefs_lookup_back(dvp, nm, vpp, 0, cr);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* By now we have a valid cached front file that we can search.
|
|
*/
|
|
error = cachefs_dirlook(dcp, nm, &cookie, &flag,
|
|
&d_offset, &fileno, cr);
|
|
rw_exit(&dcp->c_statelock);
|
|
if (error == EINVAL) {
|
|
error = cachefs_lookup_back(dvp, nm, vpp, d_offset, cr);
|
|
} else if (error == 0) {
|
|
/*
|
|
* We make a cnode here and return it.
|
|
*/
|
|
error = makecachefsnode(fileno, fscp, &cookie,
|
|
(vnode_t *)NULL, cr, 0, &cp);
|
|
if (error == ESTALE) {
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
cachefs_nocache(dcp);
|
|
rw_exit(&dcp->c_statelock);
|
|
error = cachefs_lookup_back(dvp, nm, vpp, 0, cr);
|
|
} else if (error == 0) {
|
|
if (cachefs_dnlc)
|
|
dnlc_enter(CTOV(dcp), nm, CTOV(cp), cr);
|
|
*vpp = CTOV(cp);
|
|
}
|
|
} else if (error != ENOENT) {
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
cachefs_nocache(dcp);
|
|
rw_exit(&dcp->c_statelock);
|
|
error = cachefs_lookup_back(dvp, nm, vpp, 0, cr);
|
|
}
|
|
|
|
out:
|
|
rw_exit(&dcp->c_rwlock);
|
|
out1:
|
|
if (error == 0 && C_ISVDEV((*vpp)->v_type)) {
|
|
struct vnode *newvp;
|
|
|
|
newvp = specvp(*vpp, (*vpp)->v_rdev, (*vpp)->v_type, cr);
|
|
VN_RELE(*vpp);
|
|
if (newvp == NULL) {
|
|
error = ENOSYS;
|
|
} else {
|
|
*vpp = newvp;
|
|
}
|
|
}
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_lookup: EXIT error = %d\n", error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Called from cachefs_lookup when the back file system needs to be
|
|
* examined to perform the lookup.
|
|
*/
|
|
int
|
|
cachefs_lookup_back(struct vnode *dvp, char *nm, struct vnode **vpp,
|
|
u_int d_offset, cred_t *cr)
|
|
{
|
|
int error = 0;
|
|
cnode_t *cp, *dcp = VTOC(dvp);
|
|
fscache_t *fscp = C_TO_FSCACHE(dcp);
|
|
vnode_t *backvp = NULL;
|
|
struct vattr va;
|
|
struct fid cookie;
|
|
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
|
|
/*
|
|
* Do a lookup on the back FS to get the back vnode.
|
|
*/
|
|
if (dcp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp, &dcp->c_metadata.md_cookie,
|
|
dcp);
|
|
if (error)
|
|
goto out;
|
|
}
|
|
error = VOP_LOOKUP(dcp->c_backvp, nm, &backvp, (struct pathname *)NULL,
|
|
0, (vnode_t *)NULL, cr);
|
|
if (error)
|
|
goto out;
|
|
if (C_ISVDEV(backvp->v_type)) {
|
|
struct vnode *devvp = backvp;
|
|
|
|
if (VOP_REALVP(devvp, &backvp) == 0) {
|
|
VN_HOLD(backvp);
|
|
VN_RELE(devvp);
|
|
}
|
|
}
|
|
/*
|
|
* Get the cookie from the backvp.
|
|
*/
|
|
error = cachefs_getcookie(backvp, &cookie, &va, cr);
|
|
if (error)
|
|
goto out;
|
|
/*
|
|
* If the directory entry was incomplete, we can complete it now.
|
|
*/
|
|
if ((dcp->c_metadata.md_flags & MD_POPULATED) &&
|
|
((dcp->c_flags & CN_NOCACHE) == 0) &&
|
|
(dcp->c_filegrp->fg_flags & CFS_FG_WRITE)) {
|
|
cachefs_dirent_mod(dcp, d_offset, &cookie, &va.va_nodeid);
|
|
}
|
|
|
|
out:
|
|
rw_exit(&dcp->c_statelock);
|
|
|
|
/*
|
|
* Create the cnode.
|
|
*/
|
|
if (error == 0) {
|
|
error = makecachefsnode(va.va_nodeid, fscp, &cookie, backvp,
|
|
cr, 0, &cp);
|
|
if (error == 0) {
|
|
if (cachefs_dnlc)
|
|
dnlc_enter(CTOV(dcp), nm, CTOV(cp), cr);
|
|
*vpp = CTOV(cp);
|
|
}
|
|
}
|
|
|
|
if (backvp)
|
|
VN_RELE(backvp);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_create_non_local(cnode_t *dcp, char *nm, struct vattr *vap,
|
|
enum vcexcl exclusive, int mode,
|
|
cnode_t **newcpp, cred_t *cr)
|
|
{
|
|
fscache_t *fscp = C_TO_FSCACHE(dcp);
|
|
int error = 0;
|
|
vnode_t *tvp = NULL;
|
|
struct vnode *devvp = NULL;
|
|
struct fid cookie;
|
|
struct vattr va;
|
|
|
|
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_create_non_local: ENTER dcp %x excl %d \n",
|
|
(int) dcp, exclusive);
|
|
#endif
|
|
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
error = cachefs_initstate(dcp, RW_WRITER, 0, cr);
|
|
rw_exit(&dcp->c_statelock);
|
|
if (error)
|
|
goto out;
|
|
if (C_ISFS_SINGLE(fscp)) {
|
|
struct cnode *tcp;
|
|
|
|
error = cachefs_lookup(CTOV(dcp), nm, &tvp, NULL, 0, NULL, cr);
|
|
if (error == 0) {
|
|
if (tvp->v_type == VREG) {
|
|
tcp = VTOC(tvp);
|
|
mutex_enter(&tcp->c_iomutex);
|
|
while (tcp->c_ioflags & CIO_PUTPAGES) {
|
|
error = cv_wait_sig(&tcp->c_iocv,
|
|
&tcp->c_iomutex);
|
|
if (error) {
|
|
mutex_exit(&tcp->c_iomutex);
|
|
goto out;
|
|
}
|
|
}
|
|
mutex_exit(&tcp->c_iomutex);
|
|
}
|
|
VN_RELE(tvp);
|
|
tvp = NULL;
|
|
}
|
|
}
|
|
*newcpp = NULL;
|
|
error = VOP_CREATE(dcp->c_backvp, nm, vap, exclusive, mode, &devvp, cr);
|
|
if (error)
|
|
goto out;
|
|
if (VOP_REALVP(devvp, &tvp) == 0) {
|
|
VN_HOLD(tvp);
|
|
VN_RELE(devvp);
|
|
} else {
|
|
tvp = devvp;
|
|
}
|
|
error = cachefs_getcookie(tvp, &cookie, &va, cr);
|
|
if (error)
|
|
goto out;
|
|
error = makecachefsnode(va.va_nodeid, fscp, &cookie,
|
|
tvp, cr, 0, newcpp);
|
|
if (error)
|
|
goto out;
|
|
if ((CTOV(*newcpp))->v_type == VREG &&
|
|
(vap->va_mask & AT_SIZE) && vap->va_size == 0) {
|
|
(void) pvn_vplist_dirty(CTOV(*newcpp), (u_int) 0,
|
|
(int (*)())NULL, B_INVAL|B_TRUNC, cr);
|
|
}
|
|
rw_enter(&(*newcpp)->c_statelock, RW_WRITER);
|
|
(*newcpp)->c_attr = va;
|
|
ASSERT((*newcpp)->c_fileno == (*newcpp)->c_attr.va_nodeid);
|
|
(*newcpp)->c_size = va.va_size;
|
|
(*newcpp)->c_flags |= CN_UPDATED;
|
|
rw_exit(&(*newcpp)->c_statelock);
|
|
|
|
/*
|
|
* Finally, we enter the created file in the parent directory.
|
|
* If the mount is a strict consistency mount, there's no need
|
|
* to do a cachefs_dirent(), since the CFSOP_MODIFY op will invalidate
|
|
* the cache anyway.
|
|
*/
|
|
rw_enter(&dcp->c_rwlock, RW_WRITER);
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
if (!C_ISFS_STRICT(fscp) && ((dcp->c_flags & CN_NOCACHE) == 0) &&
|
|
(dcp->c_metadata.md_flags & MD_POPULATED)) {
|
|
u_int eoffset;
|
|
|
|
error = cachefs_dirlook(dcp, nm, NULL, NULL,
|
|
&eoffset, NULL, cr);
|
|
if (error == ENOENT) {
|
|
error = cachefs_direnter(dcp, nm, &cookie, 0,
|
|
va.va_nodeid, -1, cr, SM_ASYNC);
|
|
if (error) {
|
|
/*
|
|
* We got some error from direnter().
|
|
* Put the directory
|
|
* in nocache mode from now on.
|
|
*/
|
|
cachefs_nocache(dcp);
|
|
error = 0;
|
|
}
|
|
dnlc_enter(CTOV(dcp), nm, CTOV((*newcpp)), cr);
|
|
} else if (error == EINVAL) {
|
|
cachefs_dirent_mod(dcp, eoffset,
|
|
&cookie, &va.va_nodeid);
|
|
error = 0;
|
|
} else if (error != 0) {
|
|
cachefs_nocache(dcp);
|
|
error = 0;
|
|
}
|
|
}
|
|
CFSOP_MODIFY_COBJECT(fscp, dcp, cr);
|
|
rw_exit(&dcp->c_statelock);
|
|
rw_exit(&dcp->c_rwlock);
|
|
|
|
out:
|
|
if (tvp != NULL)
|
|
VN_RELE(tvp);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_create_non_local: EXIT dcp %x error %d\n",
|
|
(int) dcp, error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_create(struct vnode *dvp, char *nm, struct vattr *vap,
|
|
enum vcexcl exclusive, int mode, struct vnode **vpp, cred_t *cr)
|
|
{
|
|
cnode_t *cp = NULL, *dcp = VTOC(dvp);
|
|
fscache_t *fscp = C_TO_FSCACHE(dcp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
int error = 0;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_create: ENTER dvp %x excl %d\n",
|
|
(int) dvp, exclusive);
|
|
#endif
|
|
|
|
/*
|
|
* Acquire the writer's lock on the cnode to eliminate any other naughty
|
|
* activity that might be happening on this directory.
|
|
*/
|
|
error = cachefs_create_non_local(dcp, nm, vap, exclusive,
|
|
mode, &cp, cr);
|
|
if (error)
|
|
goto out;
|
|
*vpp = CTOV(cp);
|
|
out:
|
|
|
|
if (error == 0 && C_ISVDEV((*vpp)->v_type)) {
|
|
struct vnode *newvp;
|
|
|
|
newvp = specvp(*vpp, (*vpp)->v_rdev, (*vpp)->v_type, cr);
|
|
VN_RELE(*vpp);
|
|
if (newvp == NULL) {
|
|
error = ENOSYS;
|
|
} else {
|
|
*vpp = newvp;
|
|
}
|
|
}
|
|
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_CREATE)) {
|
|
fid_t *fidp = NULL;
|
|
ino_t fileno = 0;
|
|
|
|
if (cp != NULL) {
|
|
fidp = &cp->c_metadata.md_cookie;
|
|
fileno = cp->c_fileno;
|
|
}
|
|
cachefs_log_create(cachep, error, fscp->fs_cfsvfsp,
|
|
fidp, fileno, cr->cr_uid);
|
|
}
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_create: EXIT dvp %x error %d\n",
|
|
(int) dvp, error);
|
|
#endif
|
|
|
|
return (error);
|
|
}
|
|
struct cnode *boguscp = NULL;
|
|
|
|
static int
|
|
cachefs_remove(struct vnode *dvp, char *nm, cred_t *cr)
|
|
{
|
|
cnode_t *cp, *dcp = VTOC(dvp);
|
|
vnode_t *vp = NULL;
|
|
fscache_t *fscp = C_TO_FSCACHE(dcp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
int error = 0;
|
|
struct vnode *origvp = NULL;
|
|
int quiet;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_remove: ENTER dvp %x name %s \n",
|
|
(int) dvp, nm);
|
|
#endif
|
|
|
|
if (fscp->fs_cache->c_flags & (CACHE_NOFILL | CACHE_NOCACHE)) {
|
|
ASSERT(dcp->c_flags & CN_NOCACHE);
|
|
}
|
|
|
|
error = cachefs_lookup(dvp, nm, &origvp, (struct pathname *)NULL, 0,
|
|
(vnode_t *)NULL, cr);
|
|
if (error) {
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_REMOVE)) {
|
|
struct fid foo;
|
|
|
|
bzero((caddr_t) &foo, sizeof (foo));
|
|
cachefs_log_remove(cachep, error, fscp->fs_cfsvfsp,
|
|
&foo, 0, cr->cr_uid);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
if (VOP_REALVP(origvp, &vp) != 0)
|
|
vp = origvp;
|
|
|
|
cp = VTOC(vp);
|
|
|
|
/*
|
|
* Acquire the rwlock (WRITER) on the directory to prevent other
|
|
* activity on the directory.
|
|
*/
|
|
rw_enter(&dcp->c_rwlock, RW_WRITER);
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (dcp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp, &dcp->c_metadata.md_cookie,
|
|
dcp);
|
|
if (error) {
|
|
rw_exit(&cp->c_statelock);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* determine if the cnode is about to go inactive */
|
|
dnlc_purge_vp(vp);
|
|
quiet = (vp->v_count == 1) || ((vp->v_count == 2) && cp->c_ipending);
|
|
|
|
#ifdef QUIET_TEST
|
|
if (!quiet) { /* TEST */
|
|
printf("cachefs_remove: not quiet: cp %x v_count %d"
|
|
" ipending %d\n",
|
|
(int) cp, (int) vp->v_count, cp->c_ipending);
|
|
}
|
|
#endif
|
|
|
|
if (quiet && cp->c_backvp) {
|
|
if (vp->v_pages) {
|
|
rw_exit(&cp->c_statelock);
|
|
(void) pvn_vplist_dirty(vp, (u_int) 0,
|
|
(int (*)())NULL, B_INVAL|B_TRUNC, cr);
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
}
|
|
if (vp->v_pages == NULL) {
|
|
VN_RELE(cp->c_backvp);
|
|
cp->c_backvp = NULL;
|
|
}
|
|
} else if (!quiet && cp->c_backvp == NULL) {
|
|
/*
|
|
* Must get ref to the back vnode so I/O after
|
|
* remove works.
|
|
*/
|
|
error = cachefs_getbackvp(fscp, &cp->c_metadata.md_cookie, cp);
|
|
if (error) {
|
|
rw_exit(&cp->c_statelock);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Call VOP_REMOVE(BackFS). Remove the directory entry from the
|
|
* cached directory.
|
|
*/
|
|
error = VOP_REMOVE(dcp->c_backvp, nm, cr);
|
|
if (error) {
|
|
rw_exit(&cp->c_statelock);
|
|
goto out;
|
|
}
|
|
|
|
dnlc_purge_vp(dvp);
|
|
if (cp->c_attr.va_nlink == 1)
|
|
cp->c_flags |= CN_DESTROY;
|
|
else
|
|
cp->c_flags |= CN_UPDATED;
|
|
|
|
cp->c_attr.va_nlink--;
|
|
CFSOP_MODIFY_COBJECT(fscp, cp, cr);
|
|
rw_exit(&cp->c_statelock);
|
|
/*
|
|
* The directory has been modified, so inform the consistency module
|
|
*/
|
|
CFSOP_MODIFY_COBJECT(fscp, dcp, cr);
|
|
dcp->c_flags |= CN_UPDATED;
|
|
/*
|
|
* If the mount is a strict consistency mount, there's no need
|
|
* to do a cachefs_rmdirent(), since the CFSOP_MODIFY op will invalidate
|
|
* the cache anyway.
|
|
*/
|
|
if (!(dcp->c_flags & CN_NOCACHE) && (!C_ISFS_STRICT(fscp))) {
|
|
error = cachefs_rmdirent(dcp, nm, cr);
|
|
if (error) {
|
|
cachefs_nocache(dcp);
|
|
error = 0;
|
|
}
|
|
}
|
|
|
|
out:
|
|
rw_exit(&dcp->c_statelock);
|
|
rw_exit(&dcp->c_rwlock);
|
|
VN_RELE(origvp);
|
|
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_REMOVE))
|
|
cachefs_log_remove(cachep, error, fscp->fs_cfsvfsp,
|
|
&cp->c_metadata.md_cookie, cp->c_fileno, cr->cr_uid);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_remove: EXIT dvp %x\n", (int) dvp);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_link(struct vnode *tdvp, struct vnode *svp, char *tnm, cred_t *cr)
|
|
{
|
|
cnode_t *cp = VTOC(svp), *tdcp = VTOC(tdvp);
|
|
fscache_t *fscp = VFS_TO_FSCACHE(tdvp->v_vfsp);
|
|
int error = 0;
|
|
struct vnode *realvp;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_link: ENTER svp %x tdvp %x tnm %s \n",
|
|
(int) svp, (int) tdvp, tnm);
|
|
#endif
|
|
|
|
if (fscp->fs_cache->c_flags & (CACHE_NOFILL | CACHE_NOCACHE))
|
|
ASSERT(tdcp->c_flags & CN_NOCACHE);
|
|
|
|
if (VOP_REALVP(svp, &realvp) == 0) {
|
|
svp = realvp;
|
|
cp = VTOC(svp);
|
|
}
|
|
|
|
rw_enter(&tdcp->c_rwlock, RW_WRITER);
|
|
rw_enter(&tdcp->c_statelock, RW_WRITER);
|
|
error = cachefs_initstate(tdcp, RW_WRITER, 1, cr);
|
|
if (error)
|
|
goto out;
|
|
error = CFSOP_CHECK_COBJECT(fscp, tdcp, 0, RW_WRITER, cr);
|
|
if (error)
|
|
goto out;
|
|
if (tdcp != cp)
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
error = cachefs_initstate(cp, RW_WRITER, 1, cr);
|
|
if (error) {
|
|
if (tdcp != cp)
|
|
rw_exit(&cp->c_statelock);
|
|
goto out;
|
|
}
|
|
|
|
error = VOP_LINK(tdcp->c_backvp, cp->c_backvp, tnm, cr);
|
|
if (error) {
|
|
if (tdcp != cp)
|
|
rw_exit(&cp->c_statelock);
|
|
goto out;
|
|
}
|
|
cp->c_flags |= CN_UPDATED;
|
|
/*
|
|
* If the mount is a strict consistency mount, there's no need
|
|
* to do a cachefs_dirent(), since the CFSOP_MODIFY op will invalidate
|
|
* the cache anyway.
|
|
*/
|
|
if (!C_ISFS_STRICT(fscp) && ((tdcp->c_flags & CN_NOCACHE) == 0) &&
|
|
(tdcp->c_metadata.md_flags & MD_POPULATED)) {
|
|
error = cachefs_direnter(tdcp, tnm, &cp->c_cookie, 0,
|
|
cp->c_fileno, -1, cr, SM_ASYNC);
|
|
if (error) {
|
|
cachefs_nocache(tdcp);
|
|
error = 0;
|
|
}
|
|
tdcp->c_flags |= CN_UPDATED;
|
|
}
|
|
|
|
CFSOP_MODIFY_COBJECT(fscp, cp, cr);
|
|
CFSOP_MODIFY_COBJECT(fscp, tdcp, cr);
|
|
cp->c_attr.va_mask = AT_ALL;
|
|
error = VOP_GETATTR(cp->c_backvp, &cp->c_attr, 0, cr);
|
|
ASSERT(cp->c_fileno == cp->c_attr.va_nodeid);
|
|
if (tdcp != cp)
|
|
rw_exit(&cp->c_statelock);
|
|
out:
|
|
rw_exit(&tdcp->c_statelock);
|
|
rw_exit(&tdcp->c_rwlock);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_link: EXIT svp %x tdvp %x tnm %s \n",
|
|
(int) svp, (int) tdvp, tnm);
|
|
#endif
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Serialize all renames in CFS, to avoid deadlocks - We have to hold two
|
|
* cnodes atomically.
|
|
*/
|
|
kmutex_t cachefs_rename_lock;
|
|
|
|
static int
|
|
cachefs_rename(struct vnode *odvp, char *onm, struct vnode *ndvp,
|
|
char *nnm, cred_t *cr)
|
|
{
|
|
cnode_t *odcp = VTOC(odvp);
|
|
cnode_t *ndcp = VTOC(ndvp);
|
|
fscache_t *fscp = C_TO_FSCACHE(odcp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
int error = 0;
|
|
struct vnode *replvp = NULL;
|
|
struct cnode *replcp;
|
|
struct fid replcookie;
|
|
struct vattr replva;
|
|
int dodelete = 0;
|
|
|
|
if (fscp->fs_cache->c_flags & (CACHE_NOFILL | CACHE_NOCACHE))
|
|
ASSERT((odcp->c_flags & CN_NOCACHE) &&
|
|
(ndcp->c_flags & CN_NOCACHE));
|
|
|
|
/*
|
|
* To avoid deadlock, we acquire this global rename lock before
|
|
* we try to get the locks for the source and target directories.
|
|
*/
|
|
mutex_enter(&cachefs_rename_lock);
|
|
rw_enter(&odcp->c_rwlock, RW_WRITER);
|
|
if (odcp != ndcp) {
|
|
rw_enter(&ndcp->c_rwlock, RW_WRITER);
|
|
}
|
|
mutex_exit(&cachefs_rename_lock);
|
|
|
|
rw_enter(&odcp->c_statelock, RW_WRITER);
|
|
error = cachefs_initstate(odcp, RW_WRITER, 1, cr);
|
|
if (error) {
|
|
rw_exit(&odcp->c_statelock);
|
|
goto out;
|
|
}
|
|
error = CFSOP_CHECK_COBJECT(fscp, odcp, 0, RW_WRITER, cr);
|
|
if (error) {
|
|
rw_exit(&odcp->c_statelock);
|
|
goto out;
|
|
}
|
|
rw_exit(&odcp->c_statelock);
|
|
|
|
if (odcp != ndcp) {
|
|
rw_enter(&ndcp->c_statelock, RW_WRITER);
|
|
error = cachefs_initstate(ndcp, RW_WRITER, 1, cr);
|
|
if (error) {
|
|
rw_exit(&ndcp->c_statelock);
|
|
goto out;
|
|
}
|
|
error = CFSOP_CHECK_COBJECT(fscp, ndcp, 0, RW_WRITER, cr);
|
|
if (error) {
|
|
rw_exit(&ndcp->c_statelock);
|
|
goto out;
|
|
}
|
|
rw_exit(&ndcp->c_statelock);
|
|
}
|
|
|
|
/*
|
|
* see if the rename is deleting a file that previously
|
|
* existed in the target directory.
|
|
*/
|
|
replcp = NULL;
|
|
replvp = NULL;
|
|
error = VOP_LOOKUP(ndcp->c_backvp, nnm, &replvp,
|
|
(struct pathname *) NULL, 0, (vnode_t *)NULL, cr);
|
|
if (error == 0) {
|
|
error = cachefs_getcookie(replvp, &replcookie, &replva, cr);
|
|
if (error == 0) {
|
|
dodelete = 1;
|
|
} else {
|
|
VN_RELE(replvp);
|
|
replvp = NULL;
|
|
}
|
|
}
|
|
|
|
if (dodelete) {
|
|
/*
|
|
* gotta make sure that existing cnodes for this thing
|
|
* don't hang around...
|
|
*/
|
|
error = makecachefsnode(replva.va_nodeid, fscp, &replcookie,
|
|
replvp, cr, 0, &replcp);
|
|
if (error) {
|
|
VN_RELE(replvp);
|
|
replvp = NULL;
|
|
dodelete = 0;
|
|
} else {
|
|
/*
|
|
* if the target of the rename already exists and is
|
|
* a directory, we check to see if it is currently
|
|
* mounted-on. If so, we fail the rename with EBUSY.
|
|
* If not, we hold the vn_vfslock across the rename
|
|
* operation so that no mounts can come in while the
|
|
* rename is happening.
|
|
*/
|
|
struct vnode *rdvp = CTOV(replcp);
|
|
if (rdvp->v_type == VDIR) {
|
|
if (vn_vfslock(rdvp)) {
|
|
error = EBUSY;
|
|
VN_RELE(replvp);
|
|
replvp = NULL;
|
|
VN_RELE(rdvp);
|
|
goto out;
|
|
}
|
|
if (rdvp->v_vfsmountedhere) {
|
|
vn_vfsunlock(rdvp);
|
|
error = EBUSY;
|
|
VN_RELE(replvp);
|
|
replvp = NULL;
|
|
VN_RELE(rdvp);
|
|
goto out;
|
|
}
|
|
}
|
|
if ((replcp->c_attr.va_nlink != 1) &&
|
|
(replcp->c_backvp == NULL)) {
|
|
/*
|
|
* Must get ref to the back vnode so I/O after
|
|
* remove works.
|
|
*/
|
|
rw_enter(&replcp->c_statelock, RW_WRITER);
|
|
error = cachefs_getbackvp(fscp,
|
|
&replcp->c_metadata.md_cookie, replcp);
|
|
rw_exit(&replcp->c_statelock);
|
|
}
|
|
}
|
|
}
|
|
|
|
error = VOP_RENAME(odcp->c_backvp, onm, ndcp->c_backvp, nnm, cr);
|
|
if (error) {
|
|
if (dodelete) {
|
|
struct vnode *rvp = CTOV(replcp);
|
|
|
|
VN_RELE(replvp);
|
|
replvp = NULL;
|
|
if (rvp->v_type == VDIR)
|
|
vn_vfsunlock(rvp);
|
|
VN_RELE(rvp);
|
|
}
|
|
goto out;
|
|
}
|
|
dnlc_purge_vp(odvp);
|
|
dnlc_purge_vp(ndvp);
|
|
|
|
if (dodelete) {
|
|
rw_enter(&replcp->c_statelock, RW_WRITER);
|
|
if (replcp->c_attr.va_nlink == 1) {
|
|
replcp->c_flags |= CN_DESTROY;
|
|
} else {
|
|
replcp->c_flags |= CN_UPDATED;
|
|
}
|
|
replcp->c_attr.va_nlink--;
|
|
CFSOP_MODIFY_COBJECT(fscp, replcp, cr);
|
|
rw_exit(&replcp->c_statelock);
|
|
if (CTOV(replcp)->v_type == VDIR)
|
|
vn_vfsunlock(CTOV(replcp));
|
|
VN_RELE(CTOV(replcp));
|
|
replcp = NULL;
|
|
}
|
|
|
|
if (!C_ISFS_STRICT(fscp)) {
|
|
struct fid cookie;
|
|
struct fid *cookiep;
|
|
ino_t fileno = 0;
|
|
int gotdirent;
|
|
|
|
/*
|
|
* if we're not strict consistency we'll modify the
|
|
* cached directories
|
|
*/
|
|
rw_enter(&odcp->c_statelock, RW_WRITER);
|
|
gotdirent = 0;
|
|
cookiep = NULL;
|
|
if ((odcp->c_flags & CN_NOCACHE) == 0) {
|
|
if (odcp->c_metadata.md_flags & MD_POPULATED) {
|
|
error = cachefs_dirlook(odcp, onm, &cookie,
|
|
NULL, NULL, &fileno, cr);
|
|
if (error == 0 || error == EINVAL) {
|
|
gotdirent = 1;
|
|
if (error == 0)
|
|
cookiep = &cookie;
|
|
} else {
|
|
cachefs_inval_object(odcp, cr);
|
|
}
|
|
}
|
|
}
|
|
|
|
error = 0;
|
|
/*
|
|
* Remove the directory entry from the old directory and
|
|
* install it in the new directory.
|
|
*/
|
|
if (gotdirent) {
|
|
error = cachefs_rmdirent(odcp, onm, cr);
|
|
if (error) {
|
|
cachefs_nocache(odcp);
|
|
error = 0;
|
|
}
|
|
}
|
|
|
|
CFSOP_MODIFY_COBJECT(fscp, odcp, cr);
|
|
rw_exit(&odcp->c_statelock);
|
|
|
|
rw_enter(&ndcp->c_statelock, RW_WRITER);
|
|
if ((ndcp->c_flags & CN_NOCACHE) == 0 &&
|
|
(ndcp->c_metadata.md_flags & MD_POPULATED)) {
|
|
if (dodelete) {
|
|
(void) cachefs_rmdirent(ndcp, nnm, cr);
|
|
}
|
|
|
|
error = 1;
|
|
if (gotdirent) {
|
|
ASSERT(fileno != 0);
|
|
error = cachefs_direnter(ndcp, nnm, cookiep,
|
|
0, fileno, -1, cr, SM_ASYNC);
|
|
}
|
|
if (error) {
|
|
cachefs_nocache(ndcp);
|
|
error = 0;
|
|
}
|
|
}
|
|
if (odcp != ndcp)
|
|
CFSOP_MODIFY_COBJECT(fscp, ndcp, cr);
|
|
rw_exit(&ndcp->c_statelock);
|
|
} else {
|
|
/*
|
|
* in strict consistency mode, cached directories are
|
|
* invalidated
|
|
*/
|
|
rw_enter(&odcp->c_statelock, RW_WRITER);
|
|
CFSOP_MODIFY_COBJECT(fscp, odcp, cr);
|
|
rw_exit(&odcp->c_statelock);
|
|
if (odcp != ndcp) {
|
|
rw_enter(&ndcp->c_statelock, RW_WRITER);
|
|
CFSOP_MODIFY_COBJECT(fscp, ndcp, cr);
|
|
rw_exit(&ndcp->c_statelock);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (odcp != ndcp)
|
|
rw_exit(&ndcp->c_rwlock);
|
|
rw_exit(&odcp->c_rwlock);
|
|
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_RENAME)) {
|
|
struct fid gone;
|
|
|
|
bzero((caddr_t) &gone, sizeof (gone));
|
|
gone.fid_len = MAXFIDSZ;
|
|
if (replvp != NULL)
|
|
(void) VOP_FID(replvp, &gone);
|
|
|
|
cachefs_log_rename(cachep, error, fscp->fs_cfsvfsp,
|
|
&gone, 0,
|
|
dodelete && (replvp != NULL), cr->cr_uid);
|
|
}
|
|
if (replvp != NULL)
|
|
VN_RELE(replvp);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_mkdir(struct vnode *dvp, char *nm, register struct vattr *va,
|
|
struct vnode **vpp, cred_t *cr)
|
|
{
|
|
cnode_t *cp = NULL, *dcp = VTOC(dvp);
|
|
struct vnode *vp = NULL;
|
|
int error = 0;
|
|
fscache_t *fscp = C_TO_FSCACHE(dcp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
struct fid cookie;
|
|
struct vattr attr;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_mkdir: ENTER vp %x\n", (int) dvp);
|
|
#endif
|
|
|
|
if (fscp->fs_cache->c_flags & (CACHE_NOFILL | CACHE_NOCACHE))
|
|
ASSERT(dcp->c_flags & CN_NOCACHE);
|
|
|
|
rw_enter(&dcp->c_rwlock, RW_WRITER);
|
|
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
error = cachefs_initstate(dcp, RW_WRITER, 1, cr);
|
|
rw_exit(&dcp->c_statelock);
|
|
if (error)
|
|
goto out;
|
|
|
|
error = VOP_MKDIR(dcp->c_backvp, nm, va, &vp, cr);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
/*
|
|
* Next we need to get the cookie, so we can do a makecachefsnode
|
|
*/
|
|
attr.va_mask = AT_ALL;
|
|
error = cachefs_getcookie(vp, &cookie, &attr, cr);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
|
|
error = makecachefsnode(attr.va_nodeid, fscp, &cookie, vp, cr, 0, &cp);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
ASSERT(CTOV(cp)->v_type == VDIR);
|
|
*vpp = CTOV(cp);
|
|
/*
|
|
* If the mount is a strict consistency mount, there's no need
|
|
* to do a cachefs_dirent(), since the CFSOP_MODIFY op will inval
|
|
* the cache anyway.
|
|
*/
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
if (!C_ISFS_STRICT(fscp) && ((dcp->c_flags & CN_NOCACHE) == 0) &&
|
|
(dcp->c_metadata.md_flags & MD_POPULATED)) {
|
|
error = cachefs_direnter(dcp, nm, &cookie, 0, attr.va_nodeid,
|
|
-1, cr, SM_ASYNC);
|
|
if (error) {
|
|
cachefs_nocache(dcp);
|
|
error = 0;
|
|
}
|
|
dnlc_enter(dvp, nm, *vpp, cr);
|
|
} else {
|
|
dnlc_purge_vp(dvp);
|
|
}
|
|
dcp->c_attr.va_nlink++;
|
|
dcp->c_flags |= CN_UPDATED;
|
|
CFSOP_MODIFY_COBJECT(fscp, dcp, cr);
|
|
rw_exit(&dcp->c_statelock);
|
|
|
|
out:
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_mkdir: EXIT error = %d\n", error);
|
|
#endif
|
|
if (vp)
|
|
VN_RELE(vp);
|
|
|
|
rw_exit(&dcp->c_rwlock);
|
|
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_MKDIR)) {
|
|
fid_t *fidp = NULL;
|
|
ino_t fileno = 0;
|
|
|
|
if (cp != NULL) {
|
|
fidp = &cp->c_metadata.md_cookie;
|
|
fileno = cp->c_fileno;
|
|
}
|
|
|
|
cachefs_log_mkdir(cachep, error, fscp->fs_cfsvfsp,
|
|
fidp, fileno, cr->cr_uid);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_rmdir(struct vnode *dvp, char *nm, struct vnode *cdir, cred_t *cr)
|
|
{
|
|
cnode_t *cp = NULL, *dcp = VTOC(dvp);
|
|
vnode_t *vp = NULL;
|
|
int error = 0;
|
|
fscache_t *fscp = C_TO_FSCACHE(dcp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_rmdir: ENTER vp %x\n", (int) dvp);
|
|
#endif
|
|
|
|
if (fscp->fs_cache->c_flags & (CACHE_NOFILL | CACHE_NOCACHE))
|
|
ASSERT(dcp->c_flags & CN_NOCACHE);
|
|
|
|
error = cachefs_lookup(dvp, nm, &vp, (struct pathname *)NULL, 0,
|
|
(vnode_t *)NULL, cr);
|
|
if (error)
|
|
goto out;
|
|
if (vp->v_type != VDIR) {
|
|
error = ENOTDIR;
|
|
goto out;
|
|
}
|
|
if (VOP_CMP(vp, cdir)) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
cp = VTOC(vp);
|
|
|
|
rw_enter(&dcp->c_rwlock, RW_WRITER);
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (dcp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp, &dcp->c_metadata.md_cookie,
|
|
dcp);
|
|
if (error)
|
|
goto out1;
|
|
}
|
|
|
|
error = CFSOP_CHECK_COBJECT(fscp, dcp, 0, RW_WRITER, cr);
|
|
if (error)
|
|
goto out1;
|
|
|
|
if ((cp->c_attr.va_nlink != 1) && (cp->c_backvp == NULL)) {
|
|
/*
|
|
* Must get ref to the back vnode so I/O after
|
|
* remove works.
|
|
*/
|
|
error = cachefs_getbackvp(fscp, &cp->c_metadata.md_cookie, cp);
|
|
if (error) {
|
|
goto out1;
|
|
}
|
|
}
|
|
|
|
error = VOP_RMDIR(dcp->c_backvp, nm, cdir, cr);
|
|
if (error)
|
|
goto out1;
|
|
/*
|
|
* If the mount is a strict consistency mount, there's no need
|
|
* to do a cachefs_rmdirent(), since the CFSOP_MODIFY op will inval
|
|
* the cache anyway.
|
|
*/
|
|
if (!C_ISFS_STRICT(fscp) &&
|
|
(dcp->c_flags & CN_NOCACHE) == 0 &&
|
|
(dcp->c_metadata.md_flags & MD_POPULATED)) {
|
|
error = cachefs_rmdirent(dcp, nm, cr);
|
|
if (error) {
|
|
cachefs_nocache(dcp);
|
|
error = 0;
|
|
}
|
|
}
|
|
/*
|
|
* *if* the (hard) link count goes to 0, then we set the CDESTROY
|
|
* flag on the cnode. The cached object will then be destroyed
|
|
* at inactive time where the chickens come home to roost :-)
|
|
* The link cnt for directories is bumped down by 2 'cause the "."
|
|
* entry has to be elided too ! The link cnt for the parent goes down
|
|
* by 1 (because of "..").
|
|
*/
|
|
cp->c_attr.va_nlink -= 2;
|
|
dcp->c_attr.va_nlink--;
|
|
if (cp->c_attr.va_nlink == 0) {
|
|
cp->c_flags |= CN_DESTROY;
|
|
} else {
|
|
cp->c_flags |= CN_UPDATED;
|
|
}
|
|
dcp->c_flags |= CN_UPDATED;
|
|
/*
|
|
* for consistency
|
|
*/
|
|
if (C_ISFS_STRICT(fscp))
|
|
dnlc_purge_vp(dvp);
|
|
dnlc_purge_vp(vp);
|
|
CFSOP_MODIFY_COBJECT(fscp, dcp, cr);
|
|
|
|
out1:
|
|
rw_exit(&cp->c_statelock);
|
|
rw_exit(&dcp->c_statelock);
|
|
rw_exit(&dcp->c_rwlock);
|
|
out:
|
|
if (vp)
|
|
VN_RELE(vp);
|
|
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_RMDIR)) {
|
|
ino_t fileno = 0;
|
|
fid_t *fidp = NULL;
|
|
|
|
if (cp != NULL) {
|
|
fidp = &cp->c_metadata.md_cookie;
|
|
fileno = cp->c_fileno;
|
|
}
|
|
|
|
cachefs_log_rmdir(cachep, error, fscp->fs_cfsvfsp,
|
|
fidp, fileno, cr->cr_uid);
|
|
}
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_rmdir: EXIT error = %d\n", error);
|
|
#endif
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_symlink(struct vnode *dvp, char *lnm, struct vattr *tva,
|
|
char *tnm, cred_t *cr)
|
|
{
|
|
cnode_t *dcp = VTOC(dvp);
|
|
fscache_t *fscp = C_TO_FSCACHE(dcp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
int error = 0;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_symlink: ENTER dvp %x tnm %s\n",
|
|
(int) dvp, tnm);
|
|
#endif
|
|
|
|
if (fscp->fs_cache->c_flags & CACHE_NOCACHE)
|
|
ASSERT(dcp->c_flags & CN_NOCACHE);
|
|
|
|
rw_enter(&dcp->c_rwlock, RW_WRITER);
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
error = cachefs_initstate(dcp, RW_WRITER, 1, cr);
|
|
if (error)
|
|
goto out;
|
|
|
|
error = CFSOP_CHECK_COBJECT(fscp, dcp, 0, RW_WRITER, cr);
|
|
if (error)
|
|
goto out;
|
|
error = VOP_SYMLINK(dcp->c_backvp, lnm, tva, tnm, cr);
|
|
if (error)
|
|
goto out;
|
|
if (C_ISFS_STRICT(fscp))
|
|
dnlc_purge_vp(dvp);
|
|
if (dcp->c_flags & CN_NOCACHE)
|
|
goto out;
|
|
if ((dcp->c_filegrp->fg_flags & CFS_FG_WRITE) == 0) {
|
|
cachefs_nocache(dcp);
|
|
dnlc_purge_vp(dvp);
|
|
goto out;
|
|
}
|
|
CFSOP_MODIFY_COBJECT(fscp, dcp, cr);
|
|
/*
|
|
* If this is a strict consistency CFS mount, we need to muck
|
|
* with the cached directories. They'll be invalidated anyway.
|
|
*/
|
|
if (!C_ISFS_STRICT(fscp) &&
|
|
(dcp->c_flags & CN_NOCACHE) == 0 &&
|
|
(dcp->c_metadata.md_flags & MD_POPULATED)) {
|
|
vnode_t *vp = NULL;
|
|
struct vattr attr;
|
|
struct fid cookie;
|
|
|
|
(void) VOP_LOOKUP(dcp->c_backvp, lnm, &vp,
|
|
(struct pathname *)NULL, 0,
|
|
(vnode_t *)NULL, cr);
|
|
if (vp != NULL) {
|
|
error = cachefs_getcookie(vp, &cookie, &attr, cr);
|
|
VN_RELE(vp);
|
|
if (error)
|
|
goto out;
|
|
error = cachefs_direnter(dcp, lnm, &cookie, 0,
|
|
attr.va_nodeid, -1, cr, SM_ASYNC);
|
|
if (error) {
|
|
cachefs_nocache(dcp);
|
|
error = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
rw_exit(&dcp->c_statelock);
|
|
rw_exit(&dcp->c_rwlock);
|
|
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_SYMLINK))
|
|
cachefs_log_symlink(cachep, error, fscp->fs_cfsvfsp,
|
|
&dcp->c_metadata.md_cookie, dcp->c_fileno,
|
|
cr->cr_uid, strlen(tnm));
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_symlink: EXIT error = %d\n", error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_readdir(struct vnode *vp, register struct uio *uiop, cred_t *cr,
|
|
int *eofp)
|
|
{
|
|
cnode_t *dcp = VTOC(vp);
|
|
int error;
|
|
fscache_t *fscp = C_TO_FSCACHE(dcp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
int type;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_readdir: ENTER vp %x\n", (int) vp);
|
|
#endif
|
|
|
|
error = 0;
|
|
rw_enter(&dcp->c_rwlock, RW_READER);
|
|
type = RW_READER;
|
|
rw_enter(&dcp->c_statelock, type);
|
|
for (;;) {
|
|
error = CFSOP_CHECK_COBJECT(fscp, dcp, 0, type, cr);
|
|
if (error != EAGAIN)
|
|
break;
|
|
rw_exit(&dcp->c_statelock);
|
|
type = RW_WRITER;
|
|
rw_enter(&dcp->c_statelock, type);
|
|
}
|
|
if (error) {
|
|
rw_exit(&dcp->c_statelock);
|
|
goto out;
|
|
}
|
|
dcp->c_usage++;
|
|
again:
|
|
error = 0;
|
|
if (fscp->fs_cache->c_flags & CACHE_NOCACHE)
|
|
ASSERT(dcp->c_flags & CN_NOCACHE);
|
|
if ((dcp->c_flags & CN_NOCACHE) ||
|
|
(((dcp->c_metadata.md_flags & MD_POPULATED) == 0) &&
|
|
((dcp->c_filegrp->fg_flags & CFS_FG_WRITE) == 0))) {
|
|
if (dcp->c_backvp == NULL) {
|
|
if (type != RW_WRITER) {
|
|
if (rw_tryupgrade(&dcp->c_statelock) == 0) {
|
|
rw_exit(&dcp->c_statelock);
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
}
|
|
type = RW_WRITER;
|
|
}
|
|
error = cachefs_getbackvp(fscp,
|
|
&dcp->c_metadata.md_cookie, dcp);
|
|
}
|
|
rw_exit(&dcp->c_statelock);
|
|
if (error == 0)
|
|
error = cachefs_readdir_back(dcp, uiop, cr, eofp);
|
|
if (error == 0)
|
|
fscp->fs_stats.st_misses++;
|
|
} else {
|
|
if (type != RW_WRITER) {
|
|
if (rw_tryupgrade(&dcp->c_statelock) == 0) {
|
|
rw_exit(&dcp->c_statelock);
|
|
rw_enter(&dcp->c_statelock, RW_WRITER);
|
|
}
|
|
type = RW_WRITER;
|
|
}
|
|
if (dcp->c_metadata.md_flags & MD_INVALREADDIR) {
|
|
cachefs_inval_object(dcp, cr);
|
|
dcp->c_flags &= ~CN_NOCACHE;
|
|
}
|
|
if ((dcp->c_metadata.md_flags & MD_POPULATED) == 0) {
|
|
if ((error =
|
|
cachefs_initstate(dcp, type, 1, cr)) == 0) {
|
|
if ((dcp->c_metadata.md_flags & MD_FILE) &&
|
|
((dcp->c_flags & CN_NOCACHE) == 0)) {
|
|
error = cachefs_filldir(dcp, type, cr);
|
|
} else {
|
|
/*
|
|
* couldn't create frontfile. Try
|
|
* the readdir again from the backfs.
|
|
*/
|
|
ASSERT(dcp->c_flags & CN_NOCACHE);
|
|
error = EAGAIN;
|
|
}
|
|
}
|
|
}
|
|
if (error) {
|
|
cachefs_nocache(dcp);
|
|
rw_downgrade(&dcp->c_statelock);
|
|
type = RW_READER;
|
|
goto again;
|
|
}
|
|
ASSERT(dcp->c_metadata.md_flags & MD_POPULATED);
|
|
if (dcp->c_frontvp == NULL) {
|
|
error = cachefs_getfrontfile(dcp,
|
|
(struct vattr *)NULL, cr);
|
|
if (error) {
|
|
cachefs_nocache(dcp);
|
|
rw_downgrade(&dcp->c_statelock);
|
|
type = RW_READER;
|
|
goto again;
|
|
}
|
|
}
|
|
rw_exit(&dcp->c_statelock);
|
|
error = cachefs_read_dir(dcp, uiop, eofp, cr);
|
|
if (error == 0)
|
|
fscp->fs_stats.st_hits++;
|
|
}
|
|
out:
|
|
rw_exit(&dcp->c_rwlock);
|
|
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_READDIR))
|
|
cachefs_log_readdir(cachep, error, fscp->fs_cfsvfsp,
|
|
&dcp->c_metadata.md_cookie, dcp->c_fileno,
|
|
cr->cr_uid, uiop->uio_loffset, *eofp);
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_readdir: EXIT error = %d\n", error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* do a readdir from the back filesystem
|
|
*/
|
|
static int
|
|
cachefs_readdir_back(struct cnode *dcp, struct uio *uiop, cred_t *cr,
|
|
int *eofp)
|
|
{
|
|
int error;
|
|
|
|
ASSERT(RW_READ_HELD(&dcp->c_rwlock));
|
|
VOP_RWLOCK(dcp->c_backvp, 0);
|
|
error = VOP_READDIR(dcp->c_backvp, uiop, cr, eofp);
|
|
VOP_RWUNLOCK(dcp->c_backvp, 0);
|
|
return (error);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
cachefs_fid(struct vnode *vp, struct fid *fidp)
|
|
{
|
|
int error = 0;
|
|
struct cnode *cp = VTOC(vp);
|
|
|
|
rw_enter(&cp->c_statelock, RW_READER);
|
|
if (fidp->fid_len < cp->c_metadata.md_cookie.fid_len) {
|
|
fidp->fid_len = cp->c_metadata.md_cookie.fid_len;
|
|
error = ENOSPC;
|
|
} else {
|
|
bcopy((caddr_t)cp->c_metadata.md_cookie.fid_data,
|
|
(caddr_t)fidp->fid_data,
|
|
cp->c_metadata.md_cookie.fid_len);
|
|
fidp->fid_len = cp->c_metadata.md_cookie.fid_len;
|
|
}
|
|
rw_exit(&cp->c_statelock);
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
cachefs_rwlock(struct vnode *vp, int write_lock)
|
|
{
|
|
cnode_t *cp = VTOC(vp);
|
|
|
|
/*
|
|
* XXX - This is ifdef'ed out for now. The problem -
|
|
* getdents() acquires the read version of rwlock, then we come
|
|
* into cachefs_readdir() and that wants to acquire the write version
|
|
* of this lock (if its going to populate the directory). This is
|
|
* a problem, this can be solved by introducing another lock in the
|
|
* cnode.
|
|
*/
|
|
/* XXX */
|
|
if (vp->v_type != VREG)
|
|
return;
|
|
if (write_lock)
|
|
rw_enter(&cp->c_rwlock, RW_WRITER);
|
|
else
|
|
rw_enter(&cp->c_rwlock, RW_READER);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static void
|
|
cachefs_rwunlock(struct vnode *vp, int write_lock)
|
|
{
|
|
cnode_t *cp = VTOC(vp);
|
|
if (vp->v_type != VREG)
|
|
return;
|
|
rw_exit(&cp->c_rwlock);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
cachefs_seek(struct vnode *vp, offset_t ooff, offset_t *noffp)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
int cachefs_lostpage = 0;
|
|
/*
|
|
* Return all the pages from [off..off+len) in file
|
|
*/
|
|
cachefs_getpage(struct vnode *vp, offset_t off, u_int len,
|
|
u_int *protp, struct page *pl[], u_int plsz, struct seg *seg,
|
|
caddr_t addr, enum seg_rw rw, cred_t *cr)
|
|
{
|
|
cnode_t *cp = VTOC(vp);
|
|
cachefscache_t *cachep = cp->c_filegrp->fg_fscp->fs_cache;
|
|
int error;
|
|
|
|
#ifdef CFSDEBUG
|
|
u_int offx = (u_int)off;
|
|
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_getpage: ENTER vp %x off %d len %d rw %d\n",
|
|
(int) vp, offx, len, rw);
|
|
#endif
|
|
if (pl == NULL) {
|
|
error = 0;
|
|
goto out;
|
|
}
|
|
if (vp->v_flag & VNOMAP) {
|
|
error = ENOSYS;
|
|
goto out;
|
|
}
|
|
if (protp != NULL)
|
|
*protp = PROT_ALL;
|
|
|
|
if (cp->c_cred == NULL) {
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (cp->c_cred == NULL) {
|
|
cp->c_cred = cr;
|
|
crhold(cr);
|
|
}
|
|
rw_exit(&cp->c_statelock);
|
|
}
|
|
again:
|
|
/*
|
|
* If we are getting called as a side effect of an cachefs_write()
|
|
* operation the local file size might not be extended yet.
|
|
* In this case we want to be able to return pages of zeroes.
|
|
*/
|
|
if ((u_int)off + len > ((cp->c_size + PAGEOFFSET) & PAGEMASK)) {
|
|
if (seg != segkmap) {
|
|
error = EFAULT;
|
|
goto out;
|
|
}
|
|
}
|
|
if (len <= PAGESIZE)
|
|
error = cachefs_getapage(vp, (u_int)off, len, protp, pl, plsz,
|
|
seg, addr, rw, cr);
|
|
else
|
|
error = pvn_getpages(cachefs_getapage, vp, (u_int)off, len,
|
|
protp, pl, plsz, seg, addr, rw, cr);
|
|
if (((cp->c_flags & CN_NOCACHE) && (error == ENOSPC)) ||
|
|
error == EAGAIN) {
|
|
goto again;
|
|
}
|
|
out:
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_GETPAGE))
|
|
cachefs_log_getpage(cachep, error, vp->v_vfsp,
|
|
&cp->c_metadata.md_cookie, cp->c_fileno,
|
|
cr->cr_uid, off, len);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_getpage: EXIT vp %x error %d\n",
|
|
(int) vp, error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
|
|
int getpage_debug = 0;
|
|
|
|
/*
|
|
* Called from pvn_getpages or cachefs_getpage to get a particular page.
|
|
*/
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_getapage(struct vnode *vp, u_int off, u_int len, u_int *protp,
|
|
struct page *pl[], u_int plsz, struct seg *seg, caddr_t addr,
|
|
enum seg_rw rw, cred_t *cr)
|
|
{
|
|
cnode_t *cp = VTOC(vp);
|
|
page_t **ppp, *pp = NULL;
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
cachefscache_t *cachep = fscp->fs_cache;
|
|
int error = 0;
|
|
struct page **ourpl;
|
|
struct page *ourstackpl[17]; /* see ASSERT() below for 17 */
|
|
int index = 0;
|
|
int downgrade;
|
|
int type;
|
|
int popoff, popsize;
|
|
|
|
ASSERT(((DEF_POP_SIZE / PAGESIZE) + 1) <= 17);
|
|
|
|
if (fscp->fs_options.opt_popsize > DEF_POP_SIZE)
|
|
ourpl = (struct page **)
|
|
cachefs_kmem_alloc(sizeof (struct page *) *
|
|
((fscp->fs_options.opt_popsize / PAGESIZE) + 1),
|
|
KM_SLEEP);
|
|
else
|
|
ourpl = ourstackpl;
|
|
|
|
/*
|
|
* Grab the readers lock so the cnode state won't change
|
|
* while we're in here.
|
|
*/
|
|
ourpl[0] = NULL;
|
|
type = RW_READER;
|
|
rw_enter(&cp->c_statelock, RW_READER);
|
|
off = off & PAGEMASK;
|
|
again:
|
|
/*
|
|
* Look for the page
|
|
*/
|
|
if (page_exists(vp, off) == 0) {
|
|
/*
|
|
* Need to do work to get the page.
|
|
* Upgrade our lock because we are going to
|
|
* modify the state of the cnode.
|
|
*/
|
|
if (type == RW_READER) {
|
|
if (rw_tryupgrade(&cp->c_statelock)) {
|
|
type = RW_WRITER;
|
|
} else {
|
|
/*
|
|
* Couldn't upgrade the lock so we'll
|
|
* release our read lock, wait for the
|
|
* writer's lock and try to find the page again.
|
|
*/
|
|
rw_exit(&cp->c_statelock);
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
type = RW_WRITER;
|
|
goto again;
|
|
}
|
|
}
|
|
/*
|
|
* If we're in NOCACHE mode, we will need a backvp
|
|
*/
|
|
if (cp->c_flags & CN_NOCACHE) {
|
|
if (cp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
if (error)
|
|
goto out;
|
|
}
|
|
error = VOP_GETPAGE(cp->c_backvp, (offset_t)off,
|
|
PAGESIZE, protp, ourpl, PAGESIZE, seg,
|
|
addr, S_READ, cr);
|
|
if (error)
|
|
goto out;
|
|
goto getpages;
|
|
}
|
|
/*
|
|
* We need a front file. If we can't get it,
|
|
* put the cnode in NOCACHE mode and try again.
|
|
*/
|
|
if (cp->c_frontvp == NULL) {
|
|
error = cachefs_getfrontfile(cp, (struct vattr *)NULL,
|
|
cr);
|
|
if (error) {
|
|
cachefs_nocache(cp);
|
|
error = EAGAIN;
|
|
goto out;
|
|
}
|
|
}
|
|
/*
|
|
* Check if the front file needs population.
|
|
* If population is necessary, make sure we have a
|
|
* backvp as well. We will get the page from the backvp.
|
|
*/
|
|
if (cachefs_check_allocmap(cp, off) == 0) {
|
|
if (cp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
if (error)
|
|
goto out;
|
|
}
|
|
if (cp->c_filegrp->fg_flags & CFS_FG_WRITE) {
|
|
cachefs_cluster_allocmap(cp, off, &popoff,
|
|
&popsize,
|
|
fscp->fs_options.opt_popsize);
|
|
if (popsize != 0) {
|
|
error = cachefs_populate(cp, popoff,
|
|
popsize, cr);
|
|
if (error) {
|
|
cachefs_nocache(cp);
|
|
error = EAGAIN;
|
|
goto out;
|
|
}
|
|
popsize = popsize - (off - popoff);
|
|
} else {
|
|
popsize = PAGESIZE;
|
|
}
|
|
}
|
|
error = VOP_GETPAGE(cp->c_backvp, (offset_t)off,
|
|
PAGESIZE, protp, ourpl, popsize,
|
|
seg, addr, S_READ, cr);
|
|
if (error)
|
|
goto out;
|
|
fscp->fs_stats.st_misses++;
|
|
} else {
|
|
if (cp->c_flags & CN_POPULATION_PENDING) {
|
|
error = VOP_FSYNC(cp->c_frontvp, FSYNC, cr);
|
|
cp->c_flags &= ~CN_POPULATION_PENDING;
|
|
if (error) {
|
|
cachefs_nocache(cp);
|
|
error = EAGAIN;
|
|
goto out;
|
|
}
|
|
}
|
|
/*
|
|
* File was populated so we get the page from the
|
|
* frontvp
|
|
*/
|
|
error = VOP_GETPAGE(cp->c_frontvp, (offset_t)off,
|
|
PAGESIZE, protp, ourpl, PAGESIZE, seg, addr,
|
|
rw, cr);
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_GPFRONT))
|
|
cachefs_log_gpfront(cachep, error,
|
|
fscp->fs_cfsvfsp,
|
|
&cp->c_metadata.md_cookie, cp->c_fileno,
|
|
cr->cr_uid, off, PAGESIZE);
|
|
if (error) {
|
|
cachefs_nocache(cp);
|
|
error = EAGAIN;
|
|
goto out;
|
|
}
|
|
fscp->fs_stats.st_hits++;
|
|
}
|
|
getpages:
|
|
downgrade = 0;
|
|
for (ppp = ourpl; *ppp; ppp++) {
|
|
if ((*ppp)->p_offset < off) {
|
|
index++;
|
|
page_unlock(*ppp);
|
|
continue;
|
|
}
|
|
if (se_shared_lock(&((*ppp)->p_selock))) {
|
|
if (page_tryupgrade(*ppp) == 0) {
|
|
for (ppp = &ourpl[index]; *ppp; ppp++)
|
|
page_unlock(*ppp);
|
|
error = EAGAIN;
|
|
goto out;
|
|
}
|
|
downgrade = 1;
|
|
}
|
|
ASSERT(se_excl_assert((&(*ppp)->p_selock)));
|
|
if ((*ppp)->p_mapping)
|
|
hat_pageunload((*ppp));
|
|
page_rename(*ppp, vp, (*ppp)->p_offset);
|
|
}
|
|
pl[0] = ourpl[index];
|
|
pl[1] = NULL;
|
|
if (downgrade) {
|
|
page_downgrade(ourpl[index]);
|
|
}
|
|
/* Unlock the rest of the pages from the cluster */
|
|
for (ppp = &ourpl[index+1]; *ppp; ppp++)
|
|
page_unlock(*ppp);
|
|
} else {
|
|
if ((pp = page_lookup(vp, off, SE_SHARED)) == NULL) {
|
|
cachefs_lostpage++;
|
|
goto again;
|
|
}
|
|
pl[0] = pp;
|
|
pl[1] = NULL;
|
|
/* XXX increment st_hits? i don't think so, but... */
|
|
}
|
|
|
|
out:
|
|
rw_exit(&cp->c_statelock);
|
|
if (fscp->fs_options.opt_popsize > DEF_POP_SIZE)
|
|
cachefs_kmem_free((caddr_t) ourpl, sizeof (struct page *) *
|
|
((fscp->fs_options.opt_popsize / PAGESIZE) + 1));
|
|
return (error);
|
|
}
|
|
|
|
|
|
/*
|
|
* Flags are composed of {B_INVAL, B_FREE, B_DONTNEED, B_FORCE}
|
|
* If len == 0, do from off to EOF.
|
|
*
|
|
* The normal cases should be len == 0 & off == 0 (entire vp list),
|
|
* len == MAXBSIZE (from segmap_release actions), and len == PAGESIZE
|
|
* (from pageout).
|
|
*/
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_putpage(struct vnode *vp, offset_t off, u_int len,
|
|
int flags, cred_t *cr)
|
|
{
|
|
struct cnode *cp = VTOC(vp);
|
|
struct page *pp;
|
|
u_int io_off, io_len, eoff;
|
|
int error = 0;
|
|
struct fscache *fscp = C_TO_FSCACHE(cp);
|
|
struct cachefscache *cachep = fscp->fs_cache;
|
|
|
|
if (len == 0 && (flags & B_INVAL) == 0 &&
|
|
(vp->v_vfsp->vfs_flag & VFS_RDONLY)) {
|
|
return (0); /* XXX goto out, for logging? */
|
|
}
|
|
if (vp->v_pages == NULL || (off >= cp->c_size &&
|
|
(flags & B_INVAL) == 0))
|
|
return (0); /* XXX goto out, for logging? */
|
|
|
|
/*
|
|
* If this is an async putpage let a thread handle it.
|
|
*/
|
|
if (flags & B_ASYNC) {
|
|
struct cachefs_req *rp;
|
|
int tflags = (flags & ~(B_ASYNC|B_DONTNEED));
|
|
|
|
if (ttoproc(curthread) == proc_pageout) {
|
|
/*
|
|
* If this is the page daemon we
|
|
* do the push synchronously (Dangerous!) and hope
|
|
* we can free enough to keep running...
|
|
*/
|
|
flags &= ~B_ASYNC;
|
|
goto again;
|
|
}
|
|
|
|
/*
|
|
* if no flags other than B_ASYNC were set,
|
|
* we coalesce putpage requests into a single one for the
|
|
* whole file (len = off = 0). If such a request is
|
|
* already queued, we're done.
|
|
*
|
|
* If there are other flags set (e.g., B_INVAL), we don't
|
|
* attempt to coalesce and we use the specified length and
|
|
* offset.
|
|
*/
|
|
mutex_enter(&cp->c_iomutex);
|
|
if ((cp->c_ioflags & CIO_PUTPAGES) == 0 || tflags != 0) {
|
|
rp = (struct cachefs_req *)
|
|
cachefs_kmem_zalloc(sizeof (struct cachefs_req),
|
|
/*LINTED alignment okay*/
|
|
KM_SLEEP);
|
|
mutex_init(&rp->cfs_req_lock, "CFS Request Mutex",
|
|
MUTEX_DEFAULT, NULL);
|
|
rp->cfs_cmd = CFS_PUTPAGE;
|
|
rp->cfs_req_u.cu_putpage.cp_vp = vp;
|
|
if (tflags == 0) {
|
|
off = len = 0;
|
|
cp->c_ioflags |= CIO_PUTPAGES;
|
|
}
|
|
rp->cfs_req_u.cu_putpage.cp_off = off;
|
|
rp->cfs_req_u.cu_putpage.cp_len = len;
|
|
rp->cfs_req_u.cu_putpage.cp_flags = flags & ~B_ASYNC;
|
|
rp->cfs_cr = cr;
|
|
crhold(rp->cfs_cr);
|
|
VN_HOLD(vp);
|
|
cp->c_nio++;
|
|
error = cachefs_addqueue(rp,
|
|
&(C_TO_FSCACHE(cp)->fs_workq));
|
|
if (error) {
|
|
error = cachefs_addqueue(rp, &cachep->c_workq);
|
|
ASSERT(error == 0);
|
|
}
|
|
}
|
|
mutex_exit(&cp->c_iomutex);
|
|
return (0);
|
|
}
|
|
|
|
|
|
again:
|
|
if (len == 0) {
|
|
/*
|
|
* Search the entire vp list for pages >= off
|
|
*/
|
|
error = pvn_vplist_dirty(vp, off, cachefspush, flags, cr);
|
|
} else {
|
|
/*
|
|
* Do a range from [off...off + len) looking for pages
|
|
* to deal with.
|
|
*/
|
|
eoff = (u_int)off + len;
|
|
for (io_off = (u_int)off; io_off < eoff && io_off < cp->c_size;
|
|
/*LINTED io_len is set before it's used */
|
|
io_off += io_len) {
|
|
/*
|
|
* If we are not invalidating, synchronously
|
|
* freeing or writing pages use the routine
|
|
* page_lookup_nowait() to prevent reclaiming
|
|
* them from the free list.
|
|
*/
|
|
if ((flags & B_INVAL) || ((flags & B_ASYNC) == 0)) {
|
|
pp = page_lookup(vp, io_off,
|
|
(flags & (B_INVAL | B_FREE)) ?
|
|
SE_EXCL : SE_SHARED);
|
|
} else {
|
|
pp = page_lookup_nowait(vp, io_off,
|
|
(flags & B_FREE) ? SE_EXCL : SE_SHARED);
|
|
}
|
|
|
|
if (pp == NULL || pvn_getdirty(pp, flags) == 0)
|
|
io_len = PAGESIZE;
|
|
else {
|
|
error = cachefspush(vp, pp, &io_off, &io_len,
|
|
flags, cr);
|
|
if (error != 0)
|
|
break;
|
|
/*
|
|
* "io_off" and "io_len" are returned as
|
|
* the range of pages we actually wrote.
|
|
* This allows us to skip ahead more quickly
|
|
* since several pages may've been dealt
|
|
* with by this iteration of the loop.
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
if (error == 0 && off == 0 && (len == 0 || len >= cp->c_size)) {
|
|
cp->c_flags &= ~CDIRTY;
|
|
}
|
|
if ((flags & (B_INVAL | B_TRUNC)) == (B_INVAL | B_TRUNC) &&
|
|
len == 0 && off == 0 && vp->v_pages) {
|
|
panic("cachefs_putpage:"
|
|
" Can't throw away pages vp %x\n",
|
|
(int) vp);
|
|
}
|
|
|
|
out:
|
|
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_PUTPAGE))
|
|
cachefs_log_putpage(cachep, error, fscp->fs_cfsvfsp,
|
|
&cp->c_metadata.md_cookie, cp->c_fileno,
|
|
cr->cr_uid, off, len);
|
|
return (error);
|
|
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_map(struct vnode *vp,
|
|
offset_t off,
|
|
struct as *as,
|
|
caddr_t *addrp,
|
|
u_int len,
|
|
u_char prot,
|
|
u_char maxprot,
|
|
u_int flags,
|
|
cred_t *cr)
|
|
{
|
|
cnode_t *cp = VTOC(vp);
|
|
fscache_t *fscp = C_TO_FSCACHE(cp);
|
|
struct segvn_crargs vn_a;
|
|
int error;
|
|
|
|
#ifdef CFSDEBUG
|
|
u_int offx = (u_int)off;
|
|
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_map: ENTER vp %x off %d len %d flags %d \n",
|
|
(int) vp, offx, len, flags);
|
|
#endif
|
|
if (vp->v_flag & VNOMAP) {
|
|
error = ENOSYS;
|
|
goto out;
|
|
}
|
|
if ((int)off < 0 || (int)(off + len) < 0) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
if (vp->v_type != VREG) {
|
|
error = ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* If file is being locked, disallow mapping.
|
|
*/
|
|
if (vp->v_filocks != NULL) {
|
|
error = EAGAIN;
|
|
goto out;
|
|
}
|
|
if (prot & PROT_WRITE && (flags & MAP_PRIVATE) == 0) {
|
|
if (cp->c_backvp == NULL) {
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
error = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
rw_exit(&cp->c_statelock);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
}
|
|
if (C_ISFS_WRITE_AROUND(fscp)) {
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
cachefs_nocache(cp);
|
|
rw_exit(&cp->c_statelock);
|
|
}
|
|
}
|
|
as_rangelock(as);
|
|
if ((flags & MAP_FIXED) == 0) {
|
|
map_addr(addrp, len, (off_t)off, 1);
|
|
if (*addrp == NULL) {
|
|
as_rangeunlock(as);
|
|
error = ENOMEM;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/*
|
|
* User specified address - blow away any previous mappings
|
|
*/
|
|
(void) as_unmap(as, *addrp, len);
|
|
}
|
|
|
|
|
|
/*
|
|
* package up all the data passed in into a segvn_args struct and
|
|
* call as_map with segvn_create function to create a new segment
|
|
* in the address space.
|
|
*/
|
|
vn_a.vp = vp;
|
|
vn_a.offset = (u_int)off;
|
|
vn_a.type = flags & MAP_TYPE;
|
|
vn_a.prot = (u_char)prot;
|
|
vn_a.maxprot = (u_char)maxprot;
|
|
vn_a.cred = cr;
|
|
vn_a.amp = NULL;
|
|
vn_a.flags = flags & ~MAP_TYPE;
|
|
error = as_map(as, *addrp, len, segvn_create, (caddr_t)&vn_a);
|
|
as_rangeunlock(as);
|
|
out:
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_map: EXIT vp %x error %d\n", (int) vp, error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_addmap(struct vnode *vp,
|
|
offset_t off,
|
|
struct as *as,
|
|
caddr_t addr,
|
|
u_int len,
|
|
u_char prot,
|
|
u_char maxprot,
|
|
u_int flags,
|
|
cred_t *cr)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
cachefs_cmp(vp1, vp2)
|
|
struct vnode *vp1, *vp2;
|
|
{
|
|
return (vp1 == vp2);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
cachefs_frlock(struct vnode *vp, int cmd, struct flock *bfp, int flag,
|
|
offset_t offset, cred_t *cr)
|
|
{
|
|
struct cnode *cp = VTOC(vp);
|
|
int error;
|
|
struct fscache *fscp = C_TO_FSCACHE(cp);
|
|
|
|
if ((cmd != F_GETLK) && (cmd != F_SETLK) && (cmd != F_SETLKW))
|
|
return (EINVAL);
|
|
if (MANDLOCK(vp, cp->c_attr.va_mode)) {
|
|
return (fs_frlock(vp, cmd, bfp, flag, offset, cr));
|
|
} else {
|
|
if ((cp->c_flags & CN_NOCACHE) == 0) {
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
cachefs_nocache(cp);
|
|
rw_exit(&cp->c_statelock);
|
|
}
|
|
if (bfp->l_whence == 2) {
|
|
bfp->l_start += cp->c_size;
|
|
bfp->l_whence = 0;
|
|
}
|
|
error = 0;
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (cp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp,
|
|
&cp->c_metadata.md_cookie, cp);
|
|
}
|
|
rw_exit(&cp->c_statelock);
|
|
if (error)
|
|
return (error);
|
|
error = VOP_FRLOCK(cp->c_backvp, cmd, bfp, flag, offset, cr);
|
|
}
|
|
|
|
/*
|
|
* If we are setting a lock mark the vnode VNOCACHE so the page
|
|
* cache does not give inconsistent results on locked files shared
|
|
* between clients. The VNOCACHE flag is never turned off as long
|
|
* as the vnode is active because it is hard to figure out when the
|
|
* last lock is gone.
|
|
* XXX - what if some already has the vnode mapped in?
|
|
*/
|
|
if ((error == 0) && (bfp->l_type != F_UNLCK) && (cmd != F_GETLK))
|
|
vp->v_flag |= VNOCACHE;
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Free storage space associated with the specified vnode. The portion
|
|
* to be freed is specified by bfp->l_start and bfp->l_len (already
|
|
* normalized to a "whence" of 0).
|
|
*
|
|
* This is an experimental facility whose continued existence is not
|
|
* guaranteed. Currently, we only support the special case
|
|
* of l_len == 0, meaning free to end of file.
|
|
*/
|
|
/* ARGSUSED */
|
|
static int
|
|
cachefs_space(struct vnode *vp, int cmd, struct flock *bfp, int flag,
|
|
offset_t offset, cred_t *cr)
|
|
{
|
|
int error;
|
|
|
|
ASSERT(vp->v_type == VREG);
|
|
if (cmd != F_FREESP)
|
|
return (EINVAL);
|
|
|
|
if ((error = convoff(vp, bfp, 0, (off_t)offset)) == 0) {
|
|
ASSERT(bfp->l_start >= 0);
|
|
if (bfp->l_len == 0) {
|
|
struct vattr va;
|
|
|
|
va.va_size = bfp->l_start;
|
|
va.va_mask = AT_SIZE;
|
|
error = cachefs_setattr(vp, &va, 0, cr);
|
|
} else
|
|
error = EINVAL;
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_realvp(struct vnode *vp, struct vnode **vpp)
|
|
{
|
|
return (EINVAL);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_delmap(struct vnode *vp, offset_t off, struct as *as,
|
|
caddr_t addr, u_int len, u_int prot, u_int maxprot, u_int flags,
|
|
cred_t *cr)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_pageio(struct vnode *vp, page_t *pp, u_int io_off, u_int io_len,
|
|
int flags, cred_t *cr)
|
|
{
|
|
return (ENOSYS);
|
|
}
|
|
|
|
/* pin a file in the cache */
|
|
static int
|
|
cachefs_pin(struct vnode *vp, cred_t *cr)
|
|
{
|
|
struct cnode *cp;
|
|
int error;
|
|
|
|
cp = VTOC(vp);
|
|
|
|
rw_enter(&cp->c_rwlock, RW_WRITER);
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
|
|
error = cachefs_pin_locked(vp, cr);
|
|
|
|
rw_exit(&cp->c_statelock);
|
|
rw_exit(&cp->c_rwlock);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/* pin a file in the cache, rwlock and statlock must be locked */
|
|
static int
|
|
cachefs_pin_locked(struct vnode *vp, cred_t *cr)
|
|
{
|
|
struct cnode *cp;
|
|
int error;
|
|
fscache_t *fscp;
|
|
off_t off;
|
|
|
|
cp = VTOC(vp);
|
|
fscp = C_TO_FSCACHE(cp);
|
|
|
|
if ((cp->c_filegrp->fg_flags & CFS_FG_WRITE) == 0)
|
|
return (EROFS);
|
|
|
|
if (cp->c_metadata.md_flags & MD_PINNED)
|
|
return (0);
|
|
|
|
if (cp->c_flags & CN_STALE)
|
|
return (0);
|
|
|
|
if (cp->c_backvp == NULL) {
|
|
error = cachefs_getbackvp(fscp, &cp->c_metadata.md_cookie, cp);
|
|
if (error)
|
|
return (error);
|
|
}
|
|
|
|
if (cp->c_frontvp == NULL) {
|
|
error = cachefs_getfrontfile(cp, (struct vattr *)NULL, cr);
|
|
if (error)
|
|
return (error);
|
|
}
|
|
|
|
if (vp->v_type == VDIR) {
|
|
if ((cp->c_metadata.md_flags & MD_POPULATED) == 0) {
|
|
if (error = cachefs_filldir(cp, RW_WRITER, cr))
|
|
return (error);
|
|
}
|
|
} else if (vp->v_type == VREG) {
|
|
/*
|
|
* for a regular file, we just populate it and
|
|
* set the MD_PINNED bit in the metadata.
|
|
*/
|
|
for (off = 0; off < cp->c_attr.va_size; off += MAXBSIZE) {
|
|
if (!cachefs_check_allocmap(cp, off)) {
|
|
int popoff, popsize;
|
|
|
|
cachefs_cluster_allocmap(cp, off, &popoff,
|
|
&popsize, fscp->fs_options.opt_popsize);
|
|
if (popsize != 0) {
|
|
error = cachefs_populate(cp, popoff,
|
|
popsize, cr);
|
|
if (error)
|
|
return (error);
|
|
popsize = popsize - (off - popoff);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return (ENOENT);
|
|
}
|
|
|
|
/*
|
|
* active cnodes are not on the lru list (they will
|
|
* be put back on at inactive time), so we don't need
|
|
* to worry about removing it here.
|
|
*/
|
|
|
|
/* mark the cnode as pinned */
|
|
cp->c_metadata.md_flags |= MD_PINNED;
|
|
cp->c_flags |= CN_UPDATED;
|
|
(void) cachefs_lru_local(fscp->fs_cache, cp->c_metadata.md_lruno, 1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* unpin a file in the cache */
|
|
/*ARGSUSED*/
|
|
static int
|
|
cachefs_unpin(struct vnode *vp, cred_t *cr)
|
|
{
|
|
struct cnode *cp;
|
|
fscache_t *fscp;
|
|
|
|
cp = VTOC(vp);
|
|
fscp = C_TO_FSCACHE(cp);
|
|
|
|
if ((cp->c_filegrp->fg_flags & CFS_FG_WRITE) == 0)
|
|
return (EROFS);
|
|
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
|
|
if (cp->c_metadata.md_flags & MD_PINNED) {
|
|
cp->c_metadata.md_flags &= ~MD_PINNED;
|
|
cp->c_flags |= CN_UPDATED;
|
|
|
|
(void) cachefs_lru_local(fscp->fs_cache,
|
|
cp->c_metadata.md_lruno, 0);
|
|
}
|
|
|
|
rw_exit(&cp->c_statelock);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
cachefs_convert_mount(struct fscache *fscp, struct cachefs_cnvt_mnt *ccmp)
|
|
{
|
|
int error;
|
|
struct vnode *vp;
|
|
struct vfs *vfsp;
|
|
int mflag = 0;
|
|
extern struct vfs *cachefs_frontrootvfsp;
|
|
|
|
/* First, check that the specified file system is not mounted */
|
|
if (ccmp->cm_op == CFS_CM_FRONT) {
|
|
/*
|
|
* XXX: c_dirvp could be NULL if we booted off the
|
|
* disk that did not have a cache!
|
|
*/
|
|
ASSERT(cachefs_frontrootvfsp != NULL);
|
|
vfsp = cachefs_frontrootvfsp;
|
|
if (fscp->fs_cache->c_dirvp)
|
|
ASSERT(cachefs_frontrootvfsp ==
|
|
fscp->fs_cache->c_dirvp->v_vfsp);
|
|
} else {
|
|
vfsp = fscp->fs_backvfsp;
|
|
}
|
|
|
|
if (vfsp->vfs_vnodecovered != NULL)
|
|
return (EINVAL);
|
|
|
|
/* First, attempt to lookup the name */
|
|
error = lookupname(ccmp->cm_name, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp);
|
|
if (error)
|
|
return (error);
|
|
|
|
/*
|
|
* So, we now have the correct vfsp as well as the vnode that
|
|
* we are going to cover.
|
|
* We fake the mount by performing a subset of the
|
|
* operations that a normal mount would perform.
|
|
*/
|
|
|
|
if (vn_vfslock(vp)) {
|
|
VN_RELE(vp);
|
|
return (EBUSY);
|
|
}
|
|
if (vp->v_vfsmountedhere != NULL) {
|
|
vn_vfsunlock(vp);
|
|
VN_RELE(vp);
|
|
return (EBUSY);
|
|
}
|
|
if (vp->v_flag & VNOMOUNT) {
|
|
vn_vfsunlock(vp);
|
|
VN_RELE(vp);
|
|
return (EINVAL);
|
|
}
|
|
RLOCK_VFSSW();
|
|
if (error = vfs_lock(vfsp)) {
|
|
vn_vfsunlock(vp);
|
|
VN_RELE(vp);
|
|
RUNLOCK_VFSSW();
|
|
return (error);
|
|
}
|
|
dnlc_purge_vp(vp);
|
|
vfs_add(vp, vfsp, mflag);
|
|
vp->v_vfsp->vfs_nsubmounts++;
|
|
vfs_unlock(vfsp);
|
|
vn_vfsunlock(vp);
|
|
RUNLOCK_VFSSW();
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
cachefs_setsecattr(vnode_t *vp, vsecattr_t *vsec, int flag, cred_t *cr)
|
|
{
|
|
cnode_t *cp = VTOC(vp);
|
|
int error = 0;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_setsecattr: ENTER vp %x\n", (int) vp);
|
|
#endif
|
|
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (cp->c_backvp == NULL)
|
|
error = cachefs_getbackvp(C_TO_FSCACHE(cp),
|
|
&cp->c_metadata.md_cookie, cp);
|
|
rw_exit(&cp->c_statelock);
|
|
ASSERT((error != 0) || (cp->c_backvp != NULL));
|
|
|
|
if (error == 0)
|
|
error = VOP_SETSECATTR(cp->c_backvp, vsec, flag, cr);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_setsecattr: EXIT error = %d\n", error);
|
|
#endif
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cachefs_getsecattr(vnode_t *vp, vsecattr_t *vsec, int flag, cred_t *cr)
|
|
{
|
|
cnode_t *cp = VTOC(vp);
|
|
int error = 0;
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_getsecattr: ENTER vp %x\n", (int) vp);
|
|
#endif
|
|
|
|
rw_enter(&cp->c_statelock, RW_WRITER);
|
|
if (cp->c_backvp == NULL)
|
|
error = cachefs_getbackvp(C_TO_FSCACHE(cp),
|
|
&cp->c_metadata.md_cookie, cp);
|
|
rw_exit(&cp->c_statelock);
|
|
ASSERT((error != 0) || (cp->c_backvp != NULL));
|
|
|
|
if (error == 0)
|
|
error = VOP_GETSECATTR(cp->c_backvp, vsec, flag, cr);
|
|
|
|
#ifdef CFSDEBUG
|
|
CFS_DEBUG(CFSDEBUG_VOPS)
|
|
printf("cachefs_getsecattr: EXIT error = %d\n", error);
|
|
#endif
|
|
return (error);
|
|
}
|