cachefs_dir.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/param.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/cred.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/pathname.h>
#include <sys/uio.h>
#include <sys/tiuser.h>
#include <sys/sysmacros.h>
#include <sys/kmem.h>
#include <sys/ioctl.h>
#include <sys/statvfs.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/utsname.h>
#include <sys/modctl.h>
#include <sys/dirent.h>
#include <sys/fbuf.h>
#include <rpc/types.h>
#include <vm/seg.h>
#include <vm/faultcode.h>
#include <vm/hat.h>
#include <vm/seg_map.h>
#include <sys/fs/cachefs_fs.h>
#include <sys/fs/cachefs_dir.h>
#include <sys/fs/cachefs_log.h>
/* forward declarations */
static int cachefs_dir_getentrys(struct cnode *, u_offset_t, u_offset_t *,
uint_t *, uint_t, caddr_t, int *);
static int cachefs_dir_stuff(cnode_t *dcp, uint_t count, caddr_t buf,
vnode_t *frontvp, u_offset_t *offsetp, u_offset_t *fsizep);
static int cachefs_dir_extend(cnode_t *, u_offset_t *, int incr_frontblks);
static int cachefs_dir_fill_common(cnode_t *dcp, cred_t *cr,
vnode_t *frontvp, vnode_t *backvp, u_offset_t *frontsize);
static int cachefs_dir_complete(fscache_t *fscp, vnode_t *backvp,
vnode_t *frontvp, cred_t *cr, int acltoo);
/*
* cachefs_dir_look() called mainly by lookup (and create), looks up the cached
* directory for an entry and returns the information there. If the directory
* entry doesn't exist return ENOENT, if it is incomplete, return EINVAL.
* Should only call this routine if the dir is populated.
* Returns ENOTDIR if dir gets nuked because of front file problems.
*/
int
cachefs_dir_look(cnode_t *dcp, char *nm, fid_t *cookiep, uint_t *flagp,
u_offset_t *d_offsetp, cfs_cid_t *cidp)
{
int error;
struct vattr va;
u_offset_t blockoff = 0LL;
uint_t offset = 0; /* offset inside the block of size MAXBSIZE */
vnode_t *dvp;
struct fscache *fscp = C_TO_FSCACHE(dcp);
cachefscache_t *cachep = fscp->fs_cache;
int nmlen;
struct fbuf *fbp;
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_look: ENTER dcp %p nm %s\n", (void *)dcp,
nm);
#endif
ASSERT(CTOV(dcp)->v_type == VDIR);
ASSERT(dcp->c_metadata.md_flags & MD_POPULATED);
ASSERT((dcp->c_flags & CN_ASYNC_POPULATE) == 0);
if (dcp->c_frontvp == NULL)
(void) cachefs_getfrontfile(dcp);
if ((dcp->c_metadata.md_flags & MD_POPULATED) == 0) {
error = ENOTDIR;
goto out;
}
dvp = dcp->c_frontvp;
va.va_mask = AT_SIZE; /* XXX should save dir size */
error = VOP_GETATTR(dvp, &va, 0, kcred);
if (error) {
cachefs_inval_object(dcp);
error = ENOTDIR;
goto out;
}
ASSERT(va.va_size != 0LL);
nmlen = (int)strlen(nm);
while (blockoff < va.va_size) {
offset = 0;
error =
fbread(dvp, (offset_t)blockoff, MAXBSIZE, S_OTHER, &fbp);
if (error)
goto out;
while (offset < MAXBSIZE && (blockoff + offset) < va.va_size) {
struct c_dirent *dep;
dep = (struct c_dirent *)((uintptr_t)fbp->fb_addr +
offset);
if ((dep->d_flag & CDE_VALID) &&
(nmlen == dep->d_namelen) &&
strcmp(dep->d_name, nm) == 0) {
if (dep->d_flag & CDE_COMPLETE) {
if (cookiep) {
CACHEFS_FID_COPY(&dep->d_cookie,
cookiep);
}
if (flagp)
*flagp = dep->d_flag;
error = 0;
} else {
error = EINVAL;
}
if (cidp)
*cidp = dep->d_id;
if (d_offsetp)
*d_offsetp = offset + blockoff;
fbrelse(fbp, S_OTHER);
goto out;
}
ASSERT(dep->d_length != 0);
offset += dep->d_length;
}
fbrelse(fbp, S_OTHER);
blockoff += MAXBSIZE;
}
error = ENOENT;
out:
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_RFDIR))
cachefs_log_rfdir(cachep, error, fscp->fs_cfsvfsp,
&dcp->c_metadata.md_cookie, dcp->c_id.cid_fileno, 0);
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("c_dir_look: EXIT error = %d\n", error);
#endif
return (error);
}
/*
* creates a new directory and populates it with "." and ".."
*/
int
cachefs_dir_new(cnode_t *dcp, cnode_t *cp)
{
int error = 0;
struct c_dirent *dep;
u_offset_t size;
int len;
struct fbuf *fbp;
#ifdef CFSDEBUG
struct vattr va;
CFS_DEBUG(CFSDEBUG_DIR)
printf("c_dir_new: ENTER dcp %p cp %p\n", (void *)dcp,
(void *)cp);
#endif
ASSERT(MUTEX_HELD(&cp->c_statelock));
ASSERT(CTOV(cp)->v_type == VDIR);
ASSERT((cp->c_flags & CN_ASYNC_POPULATE) == 0);
ASSERT(CFS_ISFS_BACKFS_NFSV4(C_TO_FSCACHE(dcp)) == 0);
if (cp->c_frontvp == NULL) {
error = cachefs_getfrontfile(cp);
if (error)
goto out;
}
#ifdef CFSDEBUG
va.va_mask = AT_SIZE;
error = VOP_GETATTR(cp->c_frontvp, &va, 0, kcred);
if (error)
goto out;
ASSERT(va.va_size == 0);
#endif
/*
* Extend the directory by one MAXBSIZE chunk
*/
size = 0LL;
error = cachefs_dir_extend(cp, &size, 1);
if (error != 0)
goto out;
error = fbread(cp->c_frontvp, (offset_t)0, MAXBSIZE, S_OTHER, &fbp);
if (error)
goto out;
/*
* Insert "." and ".."
*/
len = (int)CDE_SIZE(".");
dep = (struct c_dirent *)fbp->fb_addr;
dep->d_length = len;
dep->d_offset = (offset_t)len;
dep->d_flag = CDE_VALID | CDE_COMPLETE;
CACHEFS_FID_COPY(&cp->c_cookie, &dep->d_cookie);
dep->d_id = cp->c_id;
dep->d_namelen = 1;
bcopy(".", dep->d_name, 2);
dep = (struct c_dirent *)((uintptr_t)fbp->fb_addr + len);
dep->d_length = MAXBSIZE - len;
dep->d_offset = MAXBSIZE;
dep->d_flag = CDE_VALID | CDE_COMPLETE;
CACHEFS_FID_COPY(&dcp->c_cookie, &dep->d_cookie);
dep->d_id = dcp->c_id;
dep->d_namelen = 2;
bcopy("..", dep->d_name, 3);
(void) fbdwrite(fbp);
#ifdef INVALREADDIR
cp->c_metadata.md_flags |= MD_POPULATED | MD_INVALREADDIR;
#else
cp->c_metadata.md_flags |= MD_POPULATED;
#endif
cp->c_flags |= CN_UPDATED | CN_NEED_FRONT_SYNC;
out:
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_new: EXIT error = %d\n", error);
#endif
return (error);
}
/*
* cachefs_dir_enter adds a new directory entry. Takes as input a fid,
* fileno and a sync flag. Most of the time, the caller is content with the
* write to the (front) directory being done async. The exception being - for
* local files, we should make sure that the directory entry is made
* synchronously. That is notified by the caller.
* issync == 0 || issync == SM_ASYNC !
*
* The new entry is inserted at the end, so that we can generate local offsets
* which are compatible with the backfs offsets (which are used when
* disconnected.
*/
int
cachefs_dir_enter(cnode_t *dcp, char *nm, fid_t *cookiep, cfs_cid_t *cidp,
int issync)
{
struct vattr va;
int offset;
u_offset_t blockoff = 0LL;
u_offset_t prev_offset;
int error = 0;
vnode_t *dvp;
struct c_dirent *dep;
uint_t esize;
u_offset_t dirsize;
struct fbuf *fbp;
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("c_dir_enter: ENTER dcp %p nm %s dirflg %x\n",
(void *)dcp, nm, dcp->c_metadata.md_flags);
#endif
ASSERT(MUTEX_HELD(&dcp->c_statelock));
ASSERT(dcp->c_metadata.md_flags & MD_POPULATED);
ASSERT((dcp->c_flags & CN_ASYNC_POPULATE) == 0);
ASSERT(CTOV(dcp)->v_type == VDIR);
ASSERT(issync == 0 || issync == SM_ASYNC);
ASSERT(strlen(nm) <= MAXNAMELEN);
if (dcp->c_frontvp == NULL)
(void) cachefs_getfrontfile(dcp);
if ((dcp->c_metadata.md_flags & MD_POPULATED) == 0) {
error = ENOTDIR;
goto out;
}
dvp = dcp->c_frontvp;
/*
* Get the current EOF for the directory(data file)
*/
va.va_mask = AT_SIZE;
error = VOP_GETATTR(dvp, &va, 0, kcred);
if (error) {
cachefs_inval_object(dcp);
error = ENOTDIR;
goto out;
}
/*
* Get the last block of the directory
*/
dirsize = va.va_size;
ASSERT(dirsize != 0LL);
ASSERT(!(dirsize & MAXBOFFSET));
ASSERT(dirsize <= MAXOFF_T);
blockoff = dirsize - MAXBSIZE;
error = fbread(dvp, (offset_t)blockoff, MAXBSIZE, S_OTHER, &fbp);
if (error)
goto out;
/*
* Find the last entry
*/
offset = 0;
prev_offset = blockoff;
for (;;) {
dep = (struct c_dirent *)((uintptr_t)fbp->fb_addr + offset);
if (offset + dep->d_length == MAXBSIZE)
break;
prev_offset = dep->d_offset;
offset += dep->d_length;
ASSERT(offset < MAXBSIZE);
}
esize = C_DIRSIZ(dep);
if (dep->d_length - esize >= CDE_SIZE(nm)) {
/*
* It has room. If the entry is not valid, we can just use
* it. Otherwise, we need to adjust its length and offset
*/
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR) {
if (prev_offset >= dep->d_offset) {
printf("cachefs_dir_enter: looks like "
"we might fail the assert\n");
printf("addr %p, offset %x, "
"prev_offset %llx, dep->d_offset %llx\n",
(void *)fbp->fb_addr, offset, prev_offset,
dep->d_offset);
offset = 0;
prev_offset = blockoff;
for (;;) {
dep = (struct c_dirent *)
((uintptr_t)fbp->fb_addr + offset);
printf("offset %x, prev_offset %llx\n",
offset, prev_offset);
printf("dep->d_offset %llx, "
"dep->d_length %x\n",
dep->d_offset, dep->d_length);
if (offset + dep->d_length == MAXBSIZE)
break;
prev_offset = dep->d_offset;
offset += dep->d_length;
}
}
}
#endif /* CFSDEBUG */
if (offset)
ASSERT(prev_offset < dep->d_offset);
if (dep->d_flag & CDE_VALID) {
dep->d_length = esize;
dep->d_offset = prev_offset + (u_offset_t)esize;
dep = (struct c_dirent *)((uintptr_t)dep + esize);
}
dep->d_length = (int)((offset_t)MAXBSIZE -
((uintptr_t)dep - (uintptr_t)fbp->fb_addr));
} else {
/*
* No room - so extend the file by one more
* MAXBSIZE chunk, and fit the entry there.
*/
fbrelse(fbp, S_OTHER);
error = cachefs_dir_extend(dcp, &dirsize, 1);
if (error != 0)
goto out;
error =
fbread(dvp, (offset_t)va.va_size, MAXBSIZE, S_OTHER, &fbp);
if (error)
goto out;
dep = (struct c_dirent *)fbp->fb_addr;
dep->d_length = MAXBSIZE;
}
/*
* Fill in the rest of the new entry
*/
dep->d_offset = dirsize;
dep->d_flag = CDE_VALID;
if (cookiep) {
dep->d_flag |= CDE_COMPLETE;
CACHEFS_FID_COPY(cookiep, &dep->d_cookie);
}
dep->d_id = *cidp;
dep->d_namelen = (ushort_t)strlen(nm);
(void) bcopy(nm, dep->d_name, dep->d_namelen + 1);
#ifdef INVALREADDIR
dcp->c_metadata.md_flags |= MD_INVALREADDIR;
#endif
dcp->c_flags |= CN_UPDATED | CN_NEED_FRONT_SYNC;
if (issync)
(void) fbwrite(fbp);
else
(void) fbdwrite(fbp);
out:
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_enter: EXIT error = %d\n", error);
#endif
return (error);
}
/*
* Quite simple, if the deleted entry is the first in the MAXBSIZE block,
* we simply mark it invalid. Otherwise, the deleted entries d_length is
* just added to the previous entry.
*/
int
cachefs_dir_rmentry(cnode_t *dcp, char *nm)
{
u_offset_t blockoff = 0LL;
int offset = 0;
struct vattr va;
int error = ENOENT;
vnode_t *dvp;
int nmlen;
struct fbuf *fbp;
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_rmentry: ENTER dcp %p nm %s\n",
(void *)dcp, nm);
#endif
ASSERT(dcp->c_metadata.md_flags & MD_POPULATED);
ASSERT((dcp->c_flags & CN_ASYNC_POPULATE) == 0);
if (dcp->c_frontvp == NULL)
(void) cachefs_getfrontfile(dcp);
if ((dcp->c_metadata.md_flags & MD_POPULATED) == 0) {
error = ENOTDIR;
goto out;
}
dvp = dcp->c_frontvp;
ASSERT(CTOV(dcp)->v_type == VDIR);
ASSERT((dcp->c_flags & CN_NOCACHE) == 0);
ASSERT(dvp != NULL);
va.va_mask = AT_SIZE;
error = VOP_GETATTR(dvp, &va, 0, kcred);
if (error) {
cachefs_inval_object(dcp);
error = ENOTDIR;
goto out;
}
ASSERT(va.va_size != 0LL);
nmlen = (int)strlen(nm);
while (blockoff < va.va_size) {
uint_t *last_len;
offset = 0;
last_len = NULL;
error =
fbread(dvp, (offset_t)blockoff, MAXBSIZE, S_OTHER, &fbp);
if (error)
goto out;
while (offset < MAXBSIZE && (blockoff + offset) < va.va_size) {
struct c_dirent *dep;
dep = (struct c_dirent *)((uintptr_t)fbp->fb_addr +
offset);
if ((dep->d_flag & CDE_VALID) &&
(nmlen == dep->d_namelen) &&
strcmp(dep->d_name, nm) == 0) {
/*
* Found the entry. If this was the first entry
* in the MAXBSIZE block, Mark it invalid. Else
* add it's length to the previous entry's
* length.
*/
if (last_len == NULL) {
ASSERT(offset == 0);
dep->d_flag = 0;
} else
*last_len += dep->d_length;
(void) fbdwrite(fbp);
dcp->c_flags |= CN_UPDATED | CN_NEED_FRONT_SYNC;
goto out;
}
last_len = &dep->d_length;
offset += dep->d_length;
}
fbrelse(fbp, S_OTHER);
blockoff += MAXBSIZE;
}
error = ENOENT;
out:
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_rmentry: EXIT error = %d\n", error);
#endif
return (error);
}
#if 0
/*
* This function is used only in cachefs_lookup_back() routine but
* is inside #if 0 directive in this routine. So I am keeping this
* routine also in #if 0 directive.
*/
/*
* This function fills in the cookie and file no of the directory entry
* at the offset specified by offset - In other words, makes the entry
* "complete".
*/
int
cachefs_dir_modentry(cnode_t *dcp, u_offset_t offset, fid_t *cookiep,
cfs_cid_t *cidp)
{
struct c_dirent *dep;
u_offset_t blockoff = (offset & (offset_t)MAXBMASK);
uint_t off = (uint_t)(offset & (offset_t)MAXBOFFSET);
struct fbuf *fbp;
vnode_t *dvp;
int error = 0;
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_modentry: ENTER dcp %p offset %lld\n",
(void *)dcp, offset);
#endif
ASSERT(CTOV(dcp)->v_type == VDIR);
ASSERT(dcp->c_metadata.md_flags & MD_POPULATED);
ASSERT((dcp->c_flags & CN_ASYNC_POPULATE) == 0);
if (dcp->c_frontvp == NULL)
(void) cachefs_getfrontfile(dcp);
if ((dcp->c_metadata.md_flags & MD_POPULATED) == 0) {
return;
}
dvp = dcp->c_frontvp;
error = fbread(dvp, (offset_t)blockoff, MAXBSIZE, S_OTHER, &fbp);
if (error)
goto out;
dep = (struct c_dirent *)((uintptr_t)fbp->fb_addr + off);
if (cookiep) {
dep->d_flag |= CDE_COMPLETE;
CACHEFS_FID_COPY(cookiep, &dep->d_cookie);
}
if (cidp)
dep->d_id = *cidp;
(void) fbdwrite(fbp);
dcp->c_flags |= CN_UPDATED | CN_NEED_FRONT_SYNC;
out:
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_modentry: EXIT\n");
#endif
return (error);
}
#endif /* of #if 0 */
/*
* Called by cachefs_read_dir(). Gets a bunch if directory entries into buf and
* packs them into buf.
*/
static int
cachefs_dir_getentrys(struct cnode *dcp, u_offset_t beg_off,
u_offset_t *last_offp, uint_t *cntp, uint_t bufsize,
caddr_t buf, int *eofp)
{
#define DIR_ENDOFF 0x7fffffffLL
struct vattr va;
struct c_dirent *dep;
struct fbuf *fbp = NULL;
struct dirent64 *gdp;
u_offset_t blockoff;
uint_t off;
int error;
vnode_t *dvp = dcp->c_frontvp;
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_getentrys: "
"ENTER dcp %p beg_off %lld mdflags %x cflags %x\n",
dcp, beg_off, dcp->c_metadata.md_flags, dcp->c_flags);
#endif
ASSERT((dcp->c_flags & CN_ASYNC_POPULATE) == 0);
/*
* blockoff has the offset of the MAXBSIZE block that contains the
* entry to start with. off contains the offset relative to the
* begining of the MAXBSIZE block.
*/
if (eofp)
*eofp = 0;
gdp = (struct dirent64 *)buf;
*cntp = bufsize;
va.va_mask = AT_SIZE;
error = VOP_GETATTR(dvp, &va, 0, kcred);
if (error) {
*cntp = 0;
*last_offp = 0;
if (eofp)
*eofp = 1;
goto out;
}
ASSERT(va.va_size != 0LL);
if (beg_off == DIR_ENDOFF) {
*cntp = 0;
*last_offp = DIR_ENDOFF;
if (eofp)
*eofp = 1;
goto out;
}
/*
* locate the offset where we start reading.
*/
for (blockoff = 0; blockoff < va.va_size; blockoff += MAXBSIZE) {
error =
fbread(dvp, (offset_t)blockoff, MAXBSIZE, S_OTHER, &fbp);
if (error)
goto out;
dep = (struct c_dirent *)fbp->fb_addr;
off = 0;
while (off < MAXBSIZE && dep->d_offset <= beg_off) {
off += dep->d_length;
dep =
(struct c_dirent *)((uintptr_t)fbp->fb_addr + off);
}
if (off < MAXBSIZE)
break;
fbrelse(fbp, S_OTHER);
fbp = NULL;
}
if (blockoff >= va.va_size) {
*cntp = 0;
*last_offp = DIR_ENDOFF;
if (eofp)
*eofp = 1;
goto out;
}
/*
* Just load up the buffer with directory entries.
*/
for (;;) {
uint_t size;
int this_reclen;
ASSERT((uintptr_t)dep < ((uintptr_t)fbp->fb_addr + MAXBSIZE));
if (dep->d_flag & CDE_VALID) {
this_reclen = DIRENT64_RECLEN(dep->d_namelen);
size = C_DIRSIZ(dep);
ASSERT(size < MAXBSIZE);
if (this_reclen > bufsize)
break;
ASSERT(dep->d_namelen <= MAXNAMELEN);
ASSERT(dep->d_offset > (*last_offp));
gdp->d_ino = dep->d_id.cid_fileno;
gdp->d_off = dep->d_offset;
/* use strncpy(9f) to zero out uninitialized bytes */
ASSERT(strlen(dep->d_name) + 1 <=
DIRENT64_NAMELEN(this_reclen));
(void) strncpy(gdp->d_name, dep->d_name,
DIRENT64_NAMELEN(this_reclen));
gdp->d_reclen = (ushort_t)this_reclen;
bufsize -= this_reclen;
gdp = (struct dirent64 *)((uintptr_t)gdp +
gdp->d_reclen);
*last_offp = dep->d_offset;
}
/*
* Increment the offset. If we've hit EOF, fill in
* the lastoff and current entries d_off field.
*/
off += dep->d_length;
ASSERT(off <= MAXBSIZE);
if ((blockoff + off) >= va.va_size) {
*last_offp = DIR_ENDOFF;
if (eofp)
*eofp = 1;
break;
}
/*
* If off == MAXBSIZE, then we need to adjust our
* window to the next MAXBSIZE block of the directory.
* Adjust blockoff, off and map it in. Also, increment
* the directory and buffer pointers.
*/
if (off == MAXBSIZE) {
fbrelse(fbp, S_OTHER);
fbp = NULL;
off = 0;
blockoff += MAXBSIZE;
error = fbread(dvp, (offset_t)blockoff, MAXBSIZE,
S_OTHER, &fbp);
if (error)
goto out;
}
dep = (struct c_dirent *)((uintptr_t)fbp->fb_addr + off);
}
*cntp -= bufsize;
out:
/*
* Release any buffer and maping that may exist.
*/
if (fbp)
(void) fbrelse(fbp, S_OTHER);
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("ccachefs_dir_getentrys: EXIT error = %d\n", error);
#endif
return (error);
}
/*
* Called by cachefs_readdir(). Fills a directory request from the cache
*/
int
cachefs_dir_read(struct cnode *dcp, struct uio *uiop, int *eofp)
{
int error;
uint_t count;
uint_t size;
caddr_t buf;
u_offset_t next = uiop->uio_loffset;
struct fscache *fscp = C_TO_FSCACHE(dcp);
cachefscache_t *cachep = fscp->fs_cache;
caddr_t chrp, end;
dirent64_t *de;
ASSERT(CTOV(dcp)->v_type == VDIR);
ASSERT(RW_READ_HELD(&dcp->c_rwlock));
ASSERT(next <= MAXOFF_T);
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_read: ENTER dcp %p\n", (void *)dcp);
#endif
ASSERT((dcp->c_metadata.md_flags & (MD_FILE|MD_POPULATED)) ==
(MD_FILE|MD_POPULATED));
ASSERT((dcp->c_flags & CN_ASYNC_POPULATE) == 0);
if (dcp->c_frontvp == NULL)
(void) cachefs_getfrontfile(dcp);
if ((dcp->c_metadata.md_flags & MD_POPULATED) == 0) {
error = ENOTDIR;
goto out;
}
size = (uint_t)uiop->uio_resid;
buf = cachefs_kmem_alloc(size, KM_SLEEP);
error = cachefs_dir_getentrys(dcp, next, &next, &count, size,
buf, eofp);
if (error == 0 && (int)count > 0) {
ASSERT(count <= size);
if (fscp->fs_inum_size > 0) {
ino64_t newinum;
mutex_exit(&dcp->c_statelock);
mutex_enter(&fscp->fs_fslock);
end = (caddr_t)((uintptr_t)buf + count);
for (chrp = buf; chrp < end; chrp += de->d_reclen) {
de = (dirent64_t *)chrp;
newinum = cachefs_inum_real2fake(fscp,
de->d_ino);
if (newinum == 0)
newinum = cachefs_fileno_conflict(fscp,
de->d_ino);
de->d_ino = newinum;
}
mutex_exit(&fscp->fs_fslock);
mutex_enter(&dcp->c_statelock);
}
error = uiomove(buf, count, UIO_READ, uiop);
if (error == 0)
uiop->uio_loffset = next;
}
(void) cachefs_kmem_free(buf, size);
out:
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_RFDIR))
cachefs_log_rfdir(cachep, error, fscp->fs_cfsvfsp,
&dcp->c_metadata.md_cookie, dcp->c_id.cid_fileno, 0);
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_read: EXIT error = %d\n", error);
#endif
return (error);
}
/*
* Fully (including cookie) populates the directory from the back filesystem.
*/
int
cachefs_dir_fill(cnode_t *dcp, cred_t *cr)
{
int error = 0;
u_offset_t frontsize;
struct fscache *fscp = C_TO_FSCACHE(dcp);
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_fill: ENTER dcp %p\n", (void *)dcp);
#endif
ASSERT(CFS_ISFS_BACKFS_NFSV4(fscp) == 0);
ASSERT(MUTEX_HELD(&dcp->c_statelock));
/* XXX for now return success if async populate is scheduled */
if (dcp->c_flags & CN_ASYNC_POPULATE)
goto out;
/* get the back vp */
if (dcp->c_backvp == NULL) {
error = cachefs_getbackvp(fscp, dcp);
if (error) {
goto out;
}
}
/* get the front file vp */
if (dcp->c_frontvp == NULL)
(void) cachefs_getfrontfile(dcp);
if (dcp->c_flags & CN_NOCACHE) {
error = ENOTDIR;
goto out;
}
/* if dir was modified, toss old contents */
if (dcp->c_metadata.md_flags & MD_INVALREADDIR) {
cachefs_inval_object(dcp);
if (dcp->c_flags & CN_NOCACHE) {
error = ENOTDIR;
goto out;
}
}
error = cachefs_dir_fill_common(dcp, cr,
dcp->c_frontvp, dcp->c_backvp, &frontsize);
if (error == 0)
error = cachefs_dir_complete(fscp, dcp->c_backvp,
dcp->c_frontvp, cr, 0);
if (error != 0)
goto out;
/*
* Mark the directory as not empty. Also bang the flag that says that
* this directory needs to be sync'ed on inactive.
*/
dcp->c_metadata.md_flags |= MD_POPULATED;
dcp->c_metadata.md_flags &= ~MD_INVALREADDIR;
dcp->c_flags |= CN_UPDATED | CN_NEED_FRONT_SYNC;
/*LINTED alignment okay*/
dcp->c_metadata.md_frontblks = frontsize / MAXBSIZE;
out:
if (error) {
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_INVALIDATE)
printf("c_dir_fill: invalidating %llu\n",
(u_longlong_t)dcp->c_id.cid_fileno);
#endif
cachefs_inval_object(dcp);
}
return (error);
}
/*
* Does work of populating directory.
* Must be called while holding dcp->c_statelock
*/
static int
cachefs_dir_fill_common(cnode_t *dcp, cred_t *cr,
vnode_t *frontvp, vnode_t *backvp, u_offset_t *frontsize)
{
int error = 0;
struct uio uio;
struct iovec iov;
caddr_t buf = NULL;
int count;
int eof = 0;
u_offset_t frontoff;
struct fscache *fscp = C_TO_FSCACHE(dcp);
cachefscache_t *cachep = fscp->fs_cache;
#ifdef DEBUG
int loop_count = 0;
#endif
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_fill_common: ENTER dcp %p\n", (void *)dcp);
#endif
ASSERT(MUTEX_HELD(&dcp->c_statelock));
frontoff = *frontsize = 0LL;
buf = cachefs_kmem_alloc(MAXBSIZE, KM_SLEEP);
uio.uio_iov = &iov;
uio.uio_iovcnt = 1;
uio.uio_segflg = UIO_SYSSPACE;
uio.uio_fmode = 0;
uio.uio_extflg = UIO_COPY_CACHED;
uio.uio_loffset = 0;
for (;;) {
#ifdef DEBUG
loop_count++;
#endif
/*
* Read in a buffer's worth of dirents and enter them in to the
* directory.
*/
uio.uio_resid = MAXBSIZE;
iov.iov_base = buf;
iov.iov_len = MAXBSIZE;
(void) VOP_RWLOCK(backvp, V_WRITELOCK_FALSE, NULL);
error = VOP_READDIR(backvp, &uio, cr, &eof);
VOP_RWUNLOCK(backvp, V_WRITELOCK_FALSE, NULL);
if (error)
goto out;
/*LINTED alignment okay*/
count = MAXBSIZE - (int)uio.uio_resid;
ASSERT(count >= 0);
if (count > 0) {
if (error = cachefs_dir_stuff(dcp, count, buf,
frontvp, &frontoff, frontsize))
goto out;
ASSERT((*frontsize) != 0LL);
}
if (eof || count == 0)
break;
}
if (*frontsize == 0LL) {
/* keep us from caching an empty directory */
error = EINVAL;
goto out;
}
out:
if (CACHEFS_LOG_LOGGING(cachep, CACHEFS_LOG_FILLDIR))
cachefs_log_filldir(cachep, error, fscp->fs_cfsvfsp,
&dcp->c_metadata.md_cookie, dcp->c_id.cid_fileno,
*frontsize);
if (buf)
cachefs_kmem_free(buf, (uint_t)MAXBSIZE);
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_fill: EXIT error = %d\n", error);
#endif
return (error);
}
/*
* If the directory contains only the elements "." and "..", then this returns
* 0, otherwise returns an error.
*/
int
cachefs_dir_empty(cnode_t *dcp)
{
struct vattr va;
u_offset_t blockoff = 0;
int offset;
struct fbuf *fbp;
int error;
vnode_t *dvp = dcp->c_frontvp;
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_dir_empty: ENTER dcp %p\n", (void *)dcp);
#endif
ASSERT(CTOV(dcp)->v_type == VDIR);
ASSERT(dcp->c_metadata.md_flags & MD_POPULATED);
ASSERT((dcp->c_flags & CN_ASYNC_POPULATE) == 0);
if (dcp->c_frontvp == NULL)
(void) cachefs_getfrontfile(dcp);
if ((dcp->c_metadata.md_flags & MD_POPULATED) == 0)
return (ENOTDIR);
va.va_mask = AT_SIZE;
error = VOP_GETATTR(dvp, &va, 0, kcred);
if (error)
return (ENOTDIR);
ASSERT(va.va_size != 0LL);
while (blockoff < va.va_size) {
offset = 0;
error =
fbread(dvp, (offset_t)blockoff, MAXBSIZE, S_OTHER, &fbp);
if (error)
return (error);
while (offset < MAXBSIZE && (blockoff + offset) < va.va_size) {
struct c_dirent *dep;
dep = (struct c_dirent *)((uintptr_t)fbp->fb_addr +
offset);
if ((dep->d_flag & CDE_VALID) &&
((strcmp(dep->d_name, ".") != 0) &&
(strcmp(dep->d_name, "..") != 0))) {
(void) fbrelse(fbp, S_OTHER);
return (0);
}
offset += dep->d_length;
}
(void) fbrelse(fbp, S_OTHER);
blockoff += MAXBSIZE;
}
return (EEXIST);
}
/*
* Called by cachefs_dir_fill() to stuff a buffer of dir entries into
* a front file. This is more efficient than repeated calls to
* cachefs_dir_enter, and it also allows us to maintain entries in backfs
* order (readdir requires that entry offsets be ascending).
*/
static int
cachefs_dir_stuff(cnode_t *dcp, uint_t count, caddr_t buf,
vnode_t *frontvp, u_offset_t *offsetp, u_offset_t *fsizep)
{
int error = 0;
struct fbuf *fbp;
struct c_dirent *cdep, *last;
struct dirent64 *dep;
int inblk, entsize;
u_offset_t blockoff = (*offsetp & (offset_t)MAXBMASK);
/*LINTED alignment okay*/
uint_t off = (uint_t)(*offsetp & (offset_t)MAXBOFFSET);
/*LINTED want count != 0*/
ASSERT(count > 0);
if (*offsetp >= *fsizep) {
error = cachefs_dir_extend(dcp, fsizep, 0);
if (error)
return (error);
}
ASSERT(*fsizep != 0LL);
last = NULL;
error = fbread(frontvp, (offset_t)blockoff, MAXBSIZE, S_OTHER, &fbp);
if (error)
return (error);
cdep = (struct c_dirent *)((uintptr_t)fbp->fb_addr + off);
inblk = MAXBSIZE-off;
if (*offsetp != 0) {
ASSERT(cdep->d_length == inblk);
inblk -= C_DIRSIZ(cdep);
last = cdep;
last->d_length -= inblk;
off += last->d_length;
cdep = (struct c_dirent *)((uintptr_t)fbp->fb_addr + off);
}
dep = (struct dirent64 *)buf;
/*LINTED want count != 0*/
while (count > 0) {
if (last) {
ASSERT(dep->d_off > last->d_offset);
}
entsize = (int)CDE_SIZE(dep->d_name);
if (entsize > inblk) {
if (last) {
last->d_length += inblk;
}
(void) fbwrite(fbp);
error = cachefs_dir_extend(dcp, fsizep, 0);
if (error)
return (error);
ASSERT(*fsizep != 0LL);
blockoff += MAXBSIZE;
error = fbread(frontvp, (offset_t)blockoff, MAXBSIZE,
S_OTHER, &fbp);
if (error)
return (error);
off = 0;
cdep = (struct c_dirent *)fbp->fb_addr;
inblk = MAXBSIZE;
last = NULL;
}
cdep->d_length = entsize;
cdep->d_id.cid_fileno = dep->d_ino;
cdep->d_id.cid_flags = 0;
cdep->d_namelen = (ushort_t)strlen(dep->d_name);
cdep->d_flag = CDE_VALID;
bcopy(dep->d_name, cdep->d_name, cdep->d_namelen+1);
cdep->d_offset = dep->d_off;
inblk -= entsize;
count -= dep->d_reclen;
dep = (struct dirent64 *)((uintptr_t)dep + dep->d_reclen);
*offsetp = blockoff + (u_offset_t)off;
off += entsize;
last = cdep;
cdep = (struct c_dirent *)((uintptr_t)fbp->fb_addr + off);
}
if (last) {
last->d_length += inblk;
}
(void) fbwrite(fbp);
return (error);
}
static int
cachefs_dir_extend(cnode_t *dcp, u_offset_t *cursize, int incr_frontblks)
{
struct vattr va;
cachefscache_t *cachep = C_TO_FSCACHE(dcp)->fs_cache;
int error = 0;
struct fscache *fscp = VFS_TO_FSCACHE(CTOV(dcp)->v_vfsp);
ASSERT(MUTEX_HELD(&dcp->c_statelock));
ASSERT(((*cursize) & (MAXBSIZE-1)) == 0);
va.va_mask = AT_SIZE;
va.va_size = (u_offset_t)(*cursize + MAXBSIZE);
error = cachefs_allocblocks(cachep, 1, dcp->c_metadata.md_rltype);
if (error)
return (error);
error = VOP_SETATTR(dcp->c_frontvp, &va, 0, kcred, NULL);
if (error) {
cachefs_freeblocks(cachep, 1, dcp->c_metadata.md_rltype);
return (error);
}
if (incr_frontblks)
dcp->c_metadata.md_frontblks++;
if (fscp->fs_cdconnected != CFS_CD_CONNECTED) {
dcp->c_size += MAXBSIZE;
dcp->c_attr.va_size = dcp->c_size;
}
*cursize += MAXBSIZE;
ASSERT(*cursize != 0LL);
if (incr_frontblks)
dcp->c_flags |= CN_UPDATED;
return (0);
}
int
cachefs_async_populate_dir(struct cachefs_populate_req *pop, cred_t *cr,
vnode_t *backvp, vnode_t *frontvp)
{
vnode_t *dvp = pop->cpop_vp;
struct cnode *dcp = VTOC(dvp);
u_offset_t frontsize;
int error = 0;
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_async_populate_dir: ENTER dvp %p\n",
(void *)dvp);
#endif
ASSERT(MUTEX_HELD(&dcp->c_statelock));
ASSERT(dvp->v_type == VDIR);
ASSERT((dcp->c_metadata.md_flags & MD_POPULATED) == 0);
ASSERT(dcp->c_frontvp == frontvp);
ASSERT(dcp->c_backvp == backvp);
ASSERT(CFS_ISFS_BACKFS_NFSV4(C_TO_FSCACHE(dcp)) == 0);
/* if dir was modified, toss old contents */
if (dcp->c_metadata.md_flags & MD_INVALREADDIR) {
cachefs_inval_object(dcp);
if (dcp->c_flags & CN_NOCACHE) {
error = ENOTDIR;
goto out;
} else {
dcp->c_metadata.md_flags &= ~MD_INVALREADDIR;
}
}
error = cachefs_dir_fill_common(dcp, cr, frontvp, backvp, &frontsize);
if (error != 0)
goto out;
ASSERT(frontsize != 0LL);
mutex_exit(&dcp->c_statelock);
/*
* I don't like to break lock here but cachefs_dir_complete()
* needs it.
*/
error = cachefs_dir_complete(C_TO_FSCACHE(dcp), backvp,
frontvp, cr, 1);
mutex_enter(&dcp->c_statelock);
if (error != 0)
goto out;
/* if went nocache while lock was dropped, get out */
if ((dcp->c_flags & CN_NOCACHE) || (dcp->c_frontvp == NULL)) {
error = EINVAL;
} else {
/* allocfile and allocblocks have already happened. */
dcp->c_metadata.md_frontblks = frontsize / MAXBSIZE;
}
out:
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("cachefs_async_populate_dir: EXIT error = %d\n", error);
#endif
return (error);
}
static int
cachefs_dir_complete(fscache_t *fscp, vnode_t *backvp, vnode_t *frontvp,
cred_t *cr, int acltoo)
{
struct c_dirent *dep;
caddr_t buf = kmem_alloc(MAXBSIZE, KM_SLEEP);
struct vattr va;
u_offset_t blockoff;
int offset;
u_offset_t dir_size;
struct fbuf *fbp;
cnode_t *cp;
fid_t cookie;
vnode_t *entry_vp;
int error = 0;
/*
* note: caller better not hold a c_statelock if acltoo is set.
*/
va.va_mask = AT_SIZE;
error = VOP_GETATTR(frontvp, &va, 0, cr);
if (error)
goto out;
ASSERT(va.va_size != 0LL);
dir_size = va.va_size;
ASSERT(dir_size <= MAXOFF_T);
for (blockoff = 0; blockoff < dir_size; blockoff += MAXBSIZE) {
if (error = fbread(frontvp, (offset_t)blockoff,
MAXBSIZE, S_OTHER, &fbp))
goto out;
/*
* We cannot hold any page locks across the below VOP
* operations. We thus copy the directory entries into a
* staging buffer, and release the page lock on the directory
* by calling fbrelse(). Once any necessary cnodes have
* been created, we'll reacquire the page lock with fbread()
* and copy the staging buffer back into the frontvp at
* blockoff.
*/
bcopy(fbp->fb_addr, buf, MAXBSIZE);
fbrelse(fbp, S_OTHER);
for (offset = 0;
offset < MAXBSIZE &&
(blockoff + (u_offset_t)offset) < dir_size;
offset += dep->d_length) {
dep = (struct c_dirent *)((uintptr_t)buf + offset);
ASSERT(dep->d_length != 0);
if ((dep->d_flag & (CDE_VALID | CDE_COMPLETE)) !=
CDE_VALID)
continue;
error = VOP_LOOKUP(backvp, dep->d_name,
&entry_vp, (struct pathname *)NULL, 0,
(vnode_t *)NULL, cr);
if (error) {
/* lookup on .. in / on coc gets ENOENT */
if (error == ENOENT) {
error = 0;
continue;
}
goto out;
}
error = cachefs_getcookie(entry_vp, &cookie, NULL, cr,
TRUE);
if (error) {
#ifdef CFSDEBUG
CFS_DEBUG(CFSDEBUG_DIR)
printf("\t%s: getcookie error\n",
dep->d_name);
#endif /* CFSDEBUG */
VN_RELE(entry_vp);
goto out;
}
CACHEFS_FID_COPY(&cookie, &dep->d_cookie);
dep->d_flag |= CDE_COMPLETE;
if ((! acltoo) ||
(! cachefs_vtype_aclok(entry_vp)) ||
(fscp->fs_info.fi_mntflags & CFS_NOACL)) {
VN_RELE(entry_vp);
continue;
}
error = cachefs_cnode_make(&dep->d_id, fscp, &cookie,
NULL, entry_vp, cr, 0, &cp);
VN_RELE(entry_vp);
if (error != 0)
goto out;
ASSERT(cp != NULL);
mutex_enter(&cp->c_statelock);
if ((cp->c_flags & CN_NOCACHE) ||
(cp->c_metadata.md_flags & MD_ACL)) {
mutex_exit(&cp->c_statelock);
VN_RELE(CTOV(cp));
continue;
}
(void) cachefs_cacheacl(cp, NULL);
mutex_exit(&cp->c_statelock);
VN_RELE(CTOV(cp));
}
/*
* We must now re-lock the page corresponding to the frontvp,
* and copy our staging buffer onto it.
*/
if (error = fbread(frontvp, (offset_t)blockoff,
MAXBSIZE, S_OTHER, &fbp))
goto out;
bcopy(buf, fbp->fb_addr, MAXBSIZE);
(void) fbdwrite(fbp);
}
out:
kmem_free(buf, MAXBSIZE);
return (error);
}