/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

/*
 * afs_vnop_readdir.c - afs_readdir and bulk stat
 *
 * Implements:
 * BlobScan
 * afs_readdir_move
 * afs_bulkstat_send
 * afs_readdir/afs_readdir2(HP)
 * afs_readdir1 - HP NFS version
 * 
 */

#include <afsconfig.h>
#include "afs/param.h"


#include "afs/sysincludes.h"	/* Standard vendor system headers */
#include "afsincludes.h"	/* Afs-based standard headers */
#include "afs/afs_stats.h"	/* statistics */
#include "afs/afs_cbqueue.h"
#include "afs/nfsclient.h"
#include "afs/afs_osidnlc.h"

#if defined(AFS_HPUX1122_ENV)
#define DIRPAD 7
#elif defined(AFS_NBSD40_ENV)
#define DIRPAD 7
#else
#define DIRPAD 3
#endif

/*
 * AFS readdir vnodeop and bulk stat support.
 */

/**
 * Ensure that the blob reference refers to a valid directory entry.
 * It consults the allocation map in the page header to determine
 * whether a blob is actually in use or not.
 *
 * More formally, BlobScan is supposed to return a new blob number
 * which is just like the input parameter, only it is advanced over
 * header or free blobs.
 *
 * Note that BlobScan switches pages if necessary.  BlobScan may
 * return either 0 for success or an error code.  Upon successful
 * return, the new blob value is assigned to *ablobOut.  The new
 * blob value (*ablobOut) is set to 0 when the end of the file has
 * been reached.
 *
 * BlobScan is used by the Linux port in a separate file, so it should not
 * become static.
 */
int
BlobScan(struct dcache * afile, afs_int32 ablob, int *ablobOut)
{
    afs_int32 relativeBlob;
    afs_int32 pageBlob;
    struct PageHeader *tpe;
    struct DirBuffer headerbuf;
    afs_int32 i;
    int code;

    AFS_STATCNT(BlobScan);
    /* advance ablob over free and header blobs */
    while (1) {
	pageBlob = ablob & ~(EPP - 1);	/* base blob in same page */
	code = afs_dir_GetBlob(afile, pageBlob, &headerbuf);
	if (code == ENOENT) {
	    *ablobOut = 0; /* past the end of file */
	    return 0;      /* not an error */
	}
	if (code)
	    return code;
	tpe = (struct PageHeader *)headerbuf.data;

	relativeBlob = ablob - pageBlob;	/* relative to page's first blob */
	/* first watch for headers */
	if (pageBlob == 0) {	/* first dir page has extra-big header */
	    /* first page */
	    if (relativeBlob < DHE + 1)
		relativeBlob = DHE + 1;
	} else {		/* others have one header blob */
	    if (relativeBlob == 0)
		relativeBlob = 1;
	}
	/* make sure blob is allocated */
	for (i = relativeBlob; i < EPP; i++) {
	    if (tpe->freebitmap[i >> 3] & (1 << (i & 7)))
		break;
	}
	/* now relativeBlob is the page-relative first allocated blob,
	 * or EPP (if there are none in this page). */
	DRelease(&headerbuf, 0);
	if (i != EPP) {
	    *ablobOut = i + pageBlob;
	    return 0;
	}
	ablob = pageBlob + EPP;	/* go around again */
    }
    /* never get here */
}


#if !defined(AFS_LINUX20_ENV)
/* Changes to afs_readdir which affect dcache or vcache handling or use of
 * bulk stat data should also be reflected in the Linux specific verison of
 * the readdir routine.
 */

/*
 * The kernel don't like it so much to have large stuff on the stack.
 * Here we use a watered down version of the direct struct, since
 * its not too bright to double copy the strings anyway.
*/
#if !defined(UKERNEL)
#if defined(AFS_SGI_ENV)
/* Long form for 64 bit apps and kernel requests. */
struct min_dirent {		/* miniature dirent structure */
    /* If struct dirent changes, this must too */
    ino_t d_fileno;		/* This is 32 bits for 3.5, 64 for 6.2+ */
    off64_t d_off;
    u_short d_reclen;
};
/* Short form for 32 bit apps. */
struct irix5_min_dirent {	/* miniature dirent structure */
    /* If struct dirent changes, this must too */
    afs_uint32 d_fileno;
    afs_int32 d_off;
    u_short d_reclen;
};
#ifdef AFS_SGI62_ENV
#define AFS_DIRENT32BASESIZE IRIX5_DIRENTBASESIZE
#define AFS_DIRENT64BASESIZE DIRENT64BASESIZE
#else
#define AFS_DIRENT32BASESIZE IRIX5_DIRENTBASESIZE
#define AFS_DIRENT64BASESIZE DIRENTBASESIZE
#endif /* AFS_SGI62_ENV */
#else
struct min_direct {		/* miniature direct structure */
    /* If struct direct changes, this must too */
#if defined(AFS_DARWIN80_ENV)
    ino_t d_fileno;
    u_short d_reclen;
    u_char d_type;
    u_char d_namlen;
#elif defined(AFS_NBSD40_ENV)
    ino_t d_fileno;		/* file number of entry */
    uint16_t d_reclen;		/* length of this record */
    uint16_t d_namlen;		/* length of string in d_name */
    uint8_t  d_type; 		/* file type, see below */
#elif defined(AFS_DARWIN_ENV) || defined(AFS_XBSD_ENV)
    afs_uint32 d_fileno;
    u_short d_reclen;
    u_char d_type;
    u_char d_namlen;
#elif defined(AFS_SUN5_ENV)
    afs_uint32 d_fileno;
    afs_int32 d_off;
    u_short d_reclen;
#else
#if defined(AFS_AIX32_ENV)
    afs_int32 d_off;
#elif defined(AFS_HPUX100_ENV)
    unsigned long long d_off;
#endif
    afs_uint32 d_fileno;
    u_short d_reclen;
    u_short d_namlen;
#endif
};
#endif /* AFS_SGI_ENV */

#if	defined(AFS_HPUX_ENV)
struct minnfs_direct {
    afs_int32 d_off;		/* XXX */
    afs_uint32 d_fileno;
    u_short d_reclen;
    u_short d_namlen;
};
#define NDIRSIZ_LEN(len)   ((sizeof (struct dirent)+4 - (MAXNAMLEN+1)) + (((len)+1 + DIRPAD) &~ DIRPAD))
#endif
#endif /* !defined(UKERNEL) */


/*
 *------------------------------------------------------------------------------
 *
 * Keep a stack of about 256 fids for the bulk stat call.
 * Fill it during the readdir_move.  Later empty it...
 */

#define	READDIR_STASH	AFSCBMAX
struct AFSFid afs_readdir_stash[READDIR_STASH];
int afs_rd_stash_i = 0;

/*
 *------------------------------------------------------------------------------
 *
 * afs_readdir_move.
 *	mainly a kind of macro... makes getting the struct direct
 *	out to the user space easy... could take more parameters,
 *	but now just takes what it needs.
 *
 *
*/

#if	defined(AFS_HPUX100_ENV)
#define DIRSIZ_LEN(len) \
    ((sizeof (struct __dirent) - (_MAXNAMLEN+1)) + (((len)+1 + DIRPAD) &~ DIRPAD))
#else
#if	defined(AFS_SUN5_ENV)
#define DIRSIZ_LEN(len) ((18 + (len) + 1 + 7) & ~7 )
#else
#ifdef	AFS_NBSD40_ENV
#define DIRSIZ_LEN(len) \
    ((sizeof (struct dirent) - (MAXNAMLEN+1)) + (((len)+1 + 7) & ~7))
#else
#ifdef	AFS_DIRENT
#define DIRSIZ_LEN(len) \
    ((sizeof (struct dirent) - (MAXNAMLEN+1)) + (((len)+1 + 3) &~ 3))
#else
#if defined(AFS_SGI_ENV)
#ifndef AFS_SGI53_ENV
/* SGI 5.3 and later use 32/64 bit versions of directory size. */
#define DIRSIZ_LEN(len)		DIRENTSIZE(len)
#endif
#else /* AFS_SGI_ENV */
#define DIRSIZ_LEN(len) \
    ((sizeof (struct direct) - (MAXNAMLEN+1)) + (((len)+1 + 3) &~ 3))
#endif /* AFS_SGI_ENV */
#endif /* AFS_DIRENT */
#endif /* AFS_NBSD40_ENV */
#endif /* AFS_SUN5_ENV */
#endif /* AFS_HPUX100_ENV */

#if defined(AFS_DARWIN_ENV) || defined(AFS_XBSD_ENV)
int
afs_readdir_type(struct vcache *avc, struct DirEntry *ade)
{
    struct VenusFid tfid;
    struct vcache *tvc;
    int vtype;
    tfid.Cell = avc->f.fid.Cell;
    tfid.Fid.Volume = avc->f.fid.Fid.Volume;
    tfid.Fid.Vnode = ntohl(ade->fid.vnode);
    tfid.Fid.Unique = ntohl(ade->fid.vunique);
    if ((avc->f.states & CForeign) == 0 && (ntohl(ade->fid.vnode) & 1)) {
	return DT_DIR;
    }
    ObtainReadLock(&afs_xvcache);
    if ((tvc = afs_FindVCache(&tfid, 0, 0))) {
        ReleaseReadLock(&afs_xvcache);
	if (tvc->mvstat != AFS_MVSTAT_FILE) {
	    afs_PutVCache(tvc);
	    return DT_DIR;
	} else if (((tvc->f.states) & (CStatd | CTruth))) {
	    /* CTruth will be set if the object has
	     *ever* been statd */
	    vtype = vType(tvc);
	    afs_PutVCache(tvc);
	    if (vtype == VDIR)
		return DT_DIR;
	    else if (vtype == VREG)
		return DT_REG;
	    /* Don't do this until we're sure it can't be a mtpt */
	    /* if we're CStatd and CTruth and mvstat==AFS_MVSTAT_FILE, it's a link */
	    else if (vtype == VLNK)
		return DT_LNK;
	    /* what other types does AFS support? */
	} else
	    afs_PutVCache(tvc);
    } else
        ReleaseReadLock(&afs_xvcache);
    return DT_UNKNOWN;
}
#endif

#ifdef AFS_AIX41_ENV
#define AFS_MOVE_LOCK()   AFS_GLOCK()
#define AFS_MOVE_UNLOCK() AFS_GUNLOCK()
#else
#define AFS_MOVE_LOCK()
#define AFS_MOVE_UNLOCK()
#endif
char bufofzeros[64];		/* gotta fill with something */

#ifdef AFS_SGI65_ENV
int
afs_readdir_move(struct DirEntry *de, struct vcache *vc, struct uio *auio, 
		 int slen, ssize_t rlen, afs_size_t off)
#else
int
afs_readdir_move(struct DirEntry *de, struct vcache *vc, struct uio *auio, 
		 int slen, int rlen, afs_size_t off)
#endif
{
    int code = 0;
    struct volume *tvp;
    afs_uint32 Volume = vc->f.fid.Fid.Volume;
    afs_uint32 Vnode  = de->fid.vnode;
#if	defined(AFS_SUN5_ENV)
    struct dirent64 *direntp;
#else
#if  (defined(AFS_AIX51_ENV) && defined(AFS_64BIT_KERNEL))
    struct dirent *direntp;
#endif
#endif /* AFS_SUN5_ENV */
#ifndef	AFS_SGI53_ENV
    struct min_direct sdirEntry;
#endif /* AFS_SGI53_ENV */

    AFS_STATCNT(afs_readdir_move);

#define READDIR_CORRECT_INUMS
#ifdef READDIR_CORRECT_INUMS
    if (de->name[0] == '.' && !de->name[1]) {
	/* This is the '.' entry; if we are a volume root, we need to
	 * ignore the directory and use the inum for the mount point.
	 */
	if (!FidCmp(&afs_rootFid, &vc->f.fid)) {
	    Volume = 0;
	    Vnode  = 2;
	} else if (vc->mvstat == AFS_MVSTAT_ROOT) {
	    tvp = afs_GetVolume(&vc->f.fid, 0, READ_LOCK);
	    if (tvp) {
		Volume = tvp->mtpoint.Fid.Volume;
		Vnode  = tvp->mtpoint.Fid.Vnode;
		afs_PutVolume(tvp, READ_LOCK);
	    }
	}
    }
    else if (de->name[0] == '.' && de->name[1] == '.' && !de->name[2]) {
	/* This is the '..' entry.  Getting this right is very tricky,
	 * because we might be a volume root (so our parent is in a
	 * different volume), or our parent might be a volume root
	 * (so we actually want the mount point) or BOTH! */
	if (!FidCmp(&afs_rootFid, &vc->f.fid)) {
	    /* We are the root of the AFS root, and thus our own parent */
	    Volume = 0;
	    Vnode  = 2;
	} else if (vc->mvstat == AFS_MVSTAT_ROOT) {
	    /* We are a volume root, which means our parent is in another
	     * volume.  Luckily, we should have his fid cached... */
	    if (vc->mvid.parent) {
		if (!FidCmp(&afs_rootFid, vc->mvid.parent)) {
		    /* Parent directory is the root of the AFS root */
		    Volume = 0;
		    Vnode  = 2;
		} else if (vc->mvid.parent->Fid.Vnode == 1
			   && vc->mvid.parent->Fid.Unique == 1) {
		    /* XXX The above test is evil and probably breaks DFS */
		    /* Parent directory is the target of a mount point */
		    tvp = afs_GetVolume(vc->mvid.parent, 0, READ_LOCK);
		    if (tvp) {
			Volume = tvp->mtpoint.Fid.Volume;
			Vnode  = tvp->mtpoint.Fid.Vnode;
			afs_PutVolume(tvp, READ_LOCK);
		    }
		} else {
		    /* Parent directory is not a volume root */
		    Volume = vc->mvid.parent->Fid.Volume;
		    Vnode  = vc->mvid.parent->Fid.Vnode;
		}
	    }
	} else if (de->fid.vnode == 1 && de->fid.vunique == 1) {
	    /* XXX The above test is evil and probably breaks DFS */
	    /* Parent directory is a volume root; use the right inum */
	    tvp = afs_GetVolume(&vc->f.fid, 0, READ_LOCK);
	    if (tvp) {
		if (tvp->cell == afs_rootFid.Cell
		    && tvp->volume == afs_rootFid.Fid.Volume) {
		    /* Parent directory is the root of the AFS root */
		    Volume = 0;
		    Vnode  = 2;
		} else {
		    /* Parent directory is the target of a mount point */
		    Volume = tvp->mtpoint.Fid.Volume;
		    Vnode  = tvp->mtpoint.Fid.Vnode;
		}
		afs_PutVolume(tvp, READ_LOCK);
	    }
	}
    }
#endif

#ifdef	AFS_SGI53_ENV
    {
	afs_int32 use64BitDirent;

#ifdef AFS_SGI61_ENV
#ifdef AFS_SGI62_ENV
	use64BitDirent =
	    ABI_IS(ABI_IRIX5_64, GETDENTS_ABI(OSI_GET_CURRENT_ABI(), auio));
#else
	use64BitDirent =
	    (auio->uio_segflg !=
	     UIO_USERSPACE) ? ABI_IRIX5_64 : (ABI_IS(ABI_IRIX5_64 |
						     ABI_IRIX5_N32,
						     u.u_procp->p_abi));
#endif
#else /* AFS_SGI61_ENV */
	use64BitDirent =
	    (auio->uio_segflg !=
	     UIO_USERSPACE) ? ABI_IRIX5_64 : (ABI_IS(ABI_IRIX5_64,
						     u.u_procp->p_abi));
#endif /* AFS_SGI61_ENV */

	if (use64BitDirent) {
	    struct min_dirent sdirEntry;
	    sdirEntry.d_fileno = afs_calc_inum(vc->f.fid.Cell,
	                                       Volume, ntohl(Vnode));
	    sdirEntry.d_reclen = rlen;
	    sdirEntry.d_off = (off_t) off;
	    AFS_UIOMOVE(&sdirEntry, AFS_DIRENT64BASESIZE, UIO_READ, auio,
			code);
	    if (code == 0)
		AFS_UIOMOVE(de->name, slen - 1, UIO_READ, auio, code);
	    if (code == 0)
		AFS_UIOMOVE(bufofzeros,
			    DIRENTSIZE(slen) - (AFS_DIRENT64BASESIZE + slen -
						1), UIO_READ, auio, code);
	    if (DIRENTSIZE(slen) < rlen) {
		while (DIRENTSIZE(slen) < rlen) {
		    int minLen = rlen - DIRENTSIZE(slen);
		    if (minLen > sizeof(bufofzeros))
			minLen = sizeof(bufofzeros);
		    AFS_UIOMOVE(bufofzeros, minLen, UIO_READ, auio, code);
		    rlen -= minLen;
		}
	    }
	} else {
	    struct irix5_min_dirent sdirEntry;
	    sdirEntry.d_fileno = afs_calc_inum(vc->f.fid.Cell,
	                                       Volume, ntohl(Vnode));
	    sdirEntry.d_reclen = rlen;
	    sdirEntry.d_off = (afs_int32) off;
	    AFS_UIOMOVE(&sdirEntry, AFS_DIRENT32BASESIZE, UIO_READ, auio,
			code);
	    if (code == 0)
		AFS_UIOMOVE(de->name, slen - 1, UIO_READ, auio, code);
	    if (code == 0)
		AFS_UIOMOVE(bufofzeros,
			    IRIX5_DIRENTSIZE(slen) - (AFS_DIRENT32BASESIZE +
						      slen - 1), UIO_READ,
			    auio, code);
	    if (IRIX5_DIRENTSIZE(slen) < rlen) {
		while (IRIX5_DIRENTSIZE(slen) < rlen) {
		    int minLen = rlen - IRIX5_DIRENTSIZE(slen);
		    if (minLen > sizeof(bufofzeros))
			minLen = sizeof(bufofzeros);
		    AFS_UIOMOVE(bufofzeros, minLen, UIO_READ, auio, code);
		    rlen -= minLen;
		}
	    }
	}
    }
#else /* AFS_SGI53_ENV */
#if  defined(AFS_SUN5_ENV) || (defined(AFS_AIX51_ENV) && defined(AFS_64BIT_KERNEL))
    direntp = osi_AllocLargeSpace(AFS_LRALLOCSIZ);
    direntp->d_ino = afs_calc_inum(vc->f.fid.Cell, Volume, ntohl(Vnode));
#if defined(AFS_AIX51_ENV) && defined(AFS_64BIT_KERNEL)
    direntp->d_offset = off;
    direntp->d_namlen = slen;
#else
    direntp->d_off = off;
#endif
    direntp->d_reclen = rlen;
    strcpy(direntp->d_name, de->name);
    AFS_UIOMOVE((caddr_t) direntp, rlen, UIO_READ, auio, code);
    osi_FreeLargeSpace((char *)direntp);
#else /* AFS_SUN5_ENV */
    /* Note the odd mechanism for building the inode number */
    sdirEntry.d_fileno = afs_calc_inum(vc->f.fid.Cell, Volume, ntohl(Vnode));
    sdirEntry.d_reclen = rlen;
#if !defined(AFS_SGI_ENV)
    sdirEntry.d_namlen = slen;
#endif
#if defined(AFS_AIX32_ENV) || defined(AFS_SGI_ENV)
    sdirEntry.d_off = off;
#endif
#if defined(AFS_DARWIN_ENV) || defined(AFS_XBSD_ENV)
    sdirEntry.d_type = afs_readdir_type(vc, de);
#endif

#if defined(AFS_SGI_ENV)
    AFS_UIOMOVE(&sdirEntry, DIRENTBASESIZE, UIO_READ, auio, code);
    if (code == 0)
	AFS_UIOMOVE(de->name, slen - 1, UIO_READ, auio, code);
    if (code == 0)
	AFS_UIOMOVE(bufofzeros,
		    DIRSIZ_LEN(slen) - (DIRENTBASESIZE + slen - 1), UIO_READ,
		    auio, code);
#else /* AFS_SGI_ENV */
    AFS_MOVE_UNLOCK();
#if defined(AFS_NBSD40_ENV)
    {
	struct dirent *dp;
	dp = osi_AllocLargeSpace(sizeof(struct dirent));
	memset(dp, 0, sizeof(struct dirent));
        dp->d_ino = afs_calc_inum(vc->f.fid.Cell, Volume, ntohl(Vnode));
        dp->d_namlen = slen;
        dp->d_type = afs_readdir_type(vc, de);
        strcpy(dp->d_name, de->name);
        dp->d_reclen = _DIRENT_SIZE(dp) /* rlen */;
	if ((afs_debug & AFSDEB_VNLAYER) != 0) {
	    afs_warn("%s: %s type %d slen %d rlen %d act. rlen %zu\n", __func__,
		dp->d_name, dp->d_type, slen, rlen, _DIRENT_SIZE(dp));
	}
        AFS_UIOMOVE(dp, dp->d_reclen, UIO_READ, auio, code);
        osi_FreeLargeSpace((char *)dp);
    }
#else
    AFS_UIOMOVE((char *) &sdirEntry, sizeof(sdirEntry), UIO_READ, auio, code);
    if (code == 0) {
	AFS_UIOMOVE(de->name, slen, UIO_READ, auio, code);
    }
    /* pad out the remaining characters with zeros */
    if (code == 0) {
	AFS_UIOMOVE(bufofzeros, ((slen + 1 + DIRPAD) & ~DIRPAD) - slen,
		    UIO_READ, auio, code);
    }
#endif
    AFS_MOVE_LOCK();
#endif /* AFS_SGI_ENV */
#if !defined(AFS_NBSD_ENV)
    /* pad out the difference between rlen and slen... */
    if (DIRSIZ_LEN(slen) < rlen) {
	AFS_MOVE_UNLOCK();
	while (DIRSIZ_LEN(slen) < rlen) {
	    int minLen = rlen - DIRSIZ_LEN(slen);
	    if (minLen > sizeof(bufofzeros))
		minLen = sizeof(bufofzeros);
	    AFS_UIOMOVE(bufofzeros, minLen, UIO_READ, auio, code);
	    rlen -= minLen;
	}
	AFS_MOVE_LOCK();
    }
#endif
#endif /* AFS_SUN5_ENV */
#endif /* AFS_SGI53_ENV */
    return (code);
}


/*
 *------------------------------------------------------------------------------
 *
 * Read directory entries.
 * There are some weird things to look out for here.  The uio_offset
 * field is either 0 or it is the offset returned from a previous
 * readdir.  It is an opaque value used by the server to find the
 * correct directory block to read.  The byte count must be at least
 * vtoblksz(vp) bytes.  The count field is the number of blocks to
 * read on the server.  This is advisory only, the server may return
 * only one block's worth of entries.  Entries may be compressed on
 * the server.
 *
 * This routine encodes knowledge of Vice dirs.
 */

void
afs_bulkstat_send(struct vcache *avc, struct vrequest *req)
{
    afs_rd_stash_i = 0;
}

/*
 * Here is the bad, bad, really bad news.
 * It has to do with 'offset' (seek locations).
*/

int
#if	defined(AFS_SUN5_ENV) || defined(AFS_SGI_ENV) || defined(AFS_DARWIN_ENV) || defined(AFS_XBSD_ENV)
afs_readdir(OSI_VC_DECL(avc), struct uio *auio, afs_ucred_t *acred, 
	    int *eofp)
#else
#if defined(AFS_HPUX100_ENV)
afs_readdir2(OSI_VC_DECL(avc), struct uio *auio, afs_ucred_t *acred)
#else
afs_readdir(OSI_VC_DECL(avc), struct uio *auio, afs_ucred_t *acred)
#endif
#endif
{
    struct vrequest *treq = NULL;
    struct dcache *tdc;
    afs_size_t origOffset, tlen;
    afs_int32 len;
    int code = 0;
    struct DirBuffer oldEntry, nextEntry;
    struct DirEntry *ode = 0, *nde = 0;
    int o_slen = 0, n_slen = 0;
    afs_int32 us;
    struct afs_fakestat_state fakestate;
#if defined(AFS_SGI53_ENV)
    afs_int32 use64BitDirent, dirsiz;
#endif /* defined(AFS_SGI53_ENV) */
#ifndef	AFS_HPUX_ENV
    OSI_VC_CONVERT(avc);
#else
    /*
     * XXX All the hacks for alloced sdirEntry and inlining of afs_readdir_move instead of calling
     * it is necessary for hpux due to stack problems that seem to occur when coming thru the nfs
     * translator side XXX
     */
    struct min_direct *sdirEntry = osi_AllocSmallSpace(sizeof(struct min_direct));
    afs_int32 rlen;
#endif

    /* opaque value is pointer into a vice dir; use bit map to decide
     * if the entries are in use.  Always assumed to be valid.  0 is
     * special, means start of a new dir.  Int32 inode, followed by
     * short reclen and short namelen.  Namelen does not include
     * the null byte.  Followed by null-terminated string.
     */
    AFS_STATCNT(afs_readdir);

    memset(&oldEntry, 0, sizeof(struct DirBuffer));
    memset(&nextEntry, 0, sizeof(struct DirBuffer));

#if defined(AFS_SGI53_ENV)
#ifdef AFS_SGI61_ENV
#ifdef AFS_SGI62_ENV
    use64BitDirent =
	ABI_IS(ABI_IRIX5_64, GETDENTS_ABI(OSI_GET_CURRENT_ABI(), auio));
#else
    use64BitDirent =
	(auio->uio_segflg !=
	 UIO_USERSPACE) ? ABI_IRIX5_64 : (ABI_IS(ABI_IRIX5_64 | ABI_IRIX5_N32,
						 u.u_procp->p_abi));
#endif /* AFS_SGI62_ENV */
#else /* AFS_SGI61_ENV */
    use64BitDirent =
	(auio->uio_segflg !=
	 UIO_USERSPACE) ? ABI_IRIX5_64 : (ABI_IS(ABI_IRIX5_64,
						 u.u_procp->p_abi));
#endif /* AFS_SGI61_ENV */
#endif /* defined(AFS_SGI53_ENV) */

#if	defined(AFS_SUN5_ENV) || defined(AFS_SGI_ENV) || defined(AFS_DARWIN_ENV) || defined(AFS_XBSD_ENV)
    /* Not really used by the callee so we ignore it for now */
    if (eofp)
	*eofp = 0;
#endif
#ifndef AFS_64BIT_CLIENT
    if (AfsLargeFileUio(auio)	/* file is large than 2 GB */
	||AfsLargeFileSize(AFS_UIO_OFFSET(auio), AFS_UIO_RESID(auio)))
	return EFBIG;
#endif

    if ((code = afs_CreateReq(&treq, acred))) {
#ifdef	AFS_HPUX_ENV
	osi_FreeSmallSpace((char *)sdirEntry);
#endif
	return code;
    }
    /* update the cache entry */
    afs_InitFakeStat(&fakestate);

    AFS_DISCON_LOCK();

    code = afs_EvalFakeStat(&avc, &fakestate, treq);
    if (code)
	goto done;
  tagain:
    code = afs_VerifyVCache(avc, treq);
    if (code)
	goto done;
    /* get a reference to the entire directory */
    tdc = afs_GetDCache(avc, (afs_size_t) 0, treq, &origOffset, &tlen, 1);
    if (!tdc) {
	code = EIO;
	goto done;
    }
    ObtainReadLock(&avc->lock);
    ObtainReadLock(&tdc->lock);

    /*
     * Make sure that the data in the cache is current. There are two
     * cases we need to worry about:
     * 1. The cache data is being fetched by another process.
     * 2. The cache data is no longer valid
     */
    while ((avc->f.states & CStatd)
	   && (tdc->dflags & DFFetching)
	   && hsame(avc->f.m.DataVersion, tdc->f.versionNo)) {
	afs_Trace4(afs_iclSetp, CM_TRACE_DCACHEWAIT, ICL_TYPE_STRING,
		   __FILE__, ICL_TYPE_INT32, __LINE__, ICL_TYPE_POINTER, tdc,
		   ICL_TYPE_INT32, tdc->dflags);
	ReleaseReadLock(&tdc->lock);
	ReleaseReadLock(&avc->lock);
	afs_osi_Sleep(&tdc->validPos);
	ObtainReadLock(&avc->lock);
	ObtainReadLock(&tdc->lock);
    }
    if (!(avc->f.states & CStatd)
	|| !hsame(avc->f.m.DataVersion, tdc->f.versionNo)) {
	ReleaseReadLock(&tdc->lock);
	ReleaseReadLock(&avc->lock);
	afs_PutDCache(tdc);
	goto tagain;
    }

    /*
     *  iterator for the directory reads.  Takes the AFS DirEntry
     *  structure and slams them into UFS direct structures.
     *  uses afs_readdir_move to get the struct to the user space.
     *
     *  The routine works by looking ahead one AFS directory entry.
     *  That's because the AFS entry we are currenly working with
     *  may not fit into the buffer the user has provided.  If it
     *  doesn't we have to change the size of the LAST AFS directory
     *  entry, so that it will FIT perfectly into the block the
     *  user has provided.
     *  
     *  The 'forward looking' of the code makes it a bit tough to read.
     *  Remember we need to get an entry, see if it it fits, then
     *  set it up as the LAST entry, and find the next one.
     *
     *  Tough to take: We give out an EINVAL if we don't have enough
     *  space in the buffer, and at the same time, don't have an entry
     *  to put into the buffer. This CAN happen if the first AFS entry
     *  we get can't fit into the 512 character buffer provided.  Seems
     *  it ought not happen... 
     *
     *  Assumption: don't need to use anything but one dc entry:
     *  this means the directory ought not be greater than 64k.
     */
    len = 0;
#ifdef AFS_HPUX_ENV
    auio->uio_fpflags = 0;
#endif
    while (code == 0) {
	origOffset = AFS_UIO_OFFSET(auio);
	/* scan for the next interesting entry scan for in-use blob otherwise up point at
	 * this blob note that ode, if non-zero, also represents a held dir page */
	code = BlobScan(tdc, (origOffset >> 5), &us);

	if (code == 0 && us)
	   code = afs_dir_GetVerifiedBlob(tdc, us, &nextEntry);

	if (us == 0 || code != 0) {
	    code = 0; /* Reset code - keep old failure behaviour */
	    /* failed to setup nde, return what we've got, and release ode */
	    if (len) {
		/* something to hand over. */
#ifdef	AFS_HPUX_ENV
		sdirEntry->d_fileno = afs_calc_inum(avc->f.fid.Cell,
		                                    avc->f.fid.Fid.Volume,
		                                    ntohl(ode->fid.vnode));
		sdirEntry->d_reclen = rlen = AFS_UIO_RESID(auio);
		sdirEntry->d_namlen = o_slen;
#if defined(AFS_SUN5_ENV) || defined(AFS_AIX32_ENV) || defined(AFS_HPUX100_ENV)
		sdirEntry->d_off = origOffset;
#endif
		AFS_UIOMOVE((char *)sdirEntry, sizeof(*sdirEntry), UIO_READ,
			    auio, code);
		if (code == 0)
		    AFS_UIOMOVE(ode->name, o_slen, UIO_READ, auio, code);
		/* pad out the remaining characters with zeros */
		if (code == 0) {
		    AFS_UIOMOVE(bufofzeros,
				((o_slen + 1 + DIRPAD) & ~DIRPAD) - o_slen,
				UIO_READ, auio, code);
		}
		/* pad out the difference between rlen and slen... */
		if (DIRSIZ_LEN(o_slen) < rlen) {
		    while (DIRSIZ_LEN(o_slen) < rlen) {
			int minLen = rlen - DIRSIZ_LEN(o_slen);
			if (minLen > sizeof(bufofzeros))
			    minLen = sizeof(bufofzeros);
			AFS_UIOMOVE(bufofzeros, minLen, UIO_READ, auio, code);
			rlen -= minLen;
		    }
		}
#else
		code = afs_readdir_move(ode, avc, auio, o_slen,
#if defined(AFS_SUN5_ENV) || defined(AFS_NBSD_ENV)
					len, origOffset);
#else
					AFS_UIO_RESID(auio), origOffset);
#endif
#endif /* AFS_HPUX_ENV */
#if !defined(AFS_SUN5_ENV) && !defined(AFS_NBSD_ENV)
		AFS_UIO_SETRESID(auio, 0);
#endif
	    } else {
		/* nothin to hand over */
	    }
#if	defined(AFS_SUN5_ENV) || defined(AFS_SGI_ENV) || defined(AFS_DARWIN_ENV) || defined(AFS_XBSD_ENV)
	    if (eofp)
		*eofp = 1;	/* Set it properly */
#endif
	    DRelease(&oldEntry, 0);
	    goto dirend;
	}
	nde = (struct DirEntry *)nextEntry.data;
	
	/* Do we have enough user space to carry out our mission? */
#if defined(AFS_SGI_ENV)
	n_slen = strlen(nde->name) + 1;	/* NULL terminate */
#else
	n_slen = strlen(nde->name);
#endif
#ifdef	AFS_SGI53_ENV
	dirsiz =
	    use64BitDirent ? DIRENTSIZE(n_slen) : IRIX5_DIRENTSIZE(n_slen);
	if (dirsiz >= (AFS_UIO_RESID(auio) - len)) {
#else
	if (DIRSIZ_LEN(n_slen) >= (AFS_UIO_RESID(auio) - len)) {
#endif /* AFS_SGI53_ENV */
	    /* No can do no more now; ya know... at this time */
	    DRelease(&nextEntry, 0);	/* can't use this one. */
	    if (len) {
#ifdef	AFS_HPUX_ENV
		sdirEntry->d_fileno = afs_calc_inum(avc->f.fid.Cell,
		                                    avc->f.fid.Fid.Volume,
		                                    ntohl(ode->fid.vnode));
		sdirEntry->d_reclen = rlen = AFS_UIO_RESID(auio);
		sdirEntry->d_namlen = o_slen;
#if defined(AFS_SUN5_ENV) || defined(AFS_AIX32_ENV) || defined(AFS_HPUX100_ENV)
		sdirEntry->d_off = origOffset;
#endif
		AFS_UIOMOVE((char *)sdirEntry, sizeof(*sdirEntry), UIO_READ,
			    auio, code);
		if (code == 0)
		    AFS_UIOMOVE(ode->name, o_slen, UIO_READ, auio, code);
		/* pad out the remaining characters with zeros */
		if (code == 0) {
		    AFS_UIOMOVE(bufofzeros,
				((o_slen + 1 + DIRPAD) & ~DIRPAD) - o_slen,
				UIO_READ, auio, code);
		}
		/* pad out the difference between rlen and slen... */
		if (DIRSIZ_LEN(o_slen) < rlen) {
		    while (DIRSIZ_LEN(o_slen) < rlen) {
			int minLen = rlen - DIRSIZ_LEN(o_slen);
			if (minLen > sizeof(bufofzeros))
			    minLen = sizeof(bufofzeros);
			AFS_UIOMOVE(bufofzeros, minLen, UIO_READ, auio, code);
			rlen -= minLen;
		    }
		}
#else /* AFS_HPUX_ENV */
		code =
		    afs_readdir_move(ode, avc, auio, o_slen,
				     AFS_UIO_RESID(auio), origOffset);
#endif /* AFS_HPUX_ENV */
		/* this next line used to be AFSVFS40 or AIX 3.1, but is
		 * really generic */
		AFS_UIO_SETOFFSET(auio, origOffset);
#if !defined(AFS_NBSD_ENV)
		AFS_UIO_SETRESID(auio, 0);
#endif
	    } else {		/* trouble, can't give anything to the user! */
		/* even though he has given us a buffer, 
		 * even though we have something to give us,
		 * Looks like we lost something somewhere.
		 */
		code = EINVAL;
	    }
	    DRelease(&oldEntry, 0);
	    goto dirend;
	}

	/*
	 * In any event, we move out the LAST de entry, getting ready
	 * to set up for the next one.
	 */
	if (len) {
#ifdef	AFS_HPUX_ENV
	    sdirEntry->d_fileno = afs_calc_inum(avc->f.fid.Cell,
		                                avc->f.fid.Fid.Volume,
	                                        ntohl(ode->fid.vnode));
	    sdirEntry->d_reclen = rlen = len;
	    sdirEntry->d_namlen = o_slen;
#if defined(AFS_SUN5_ENV) || defined(AFS_AIX32_ENV) || defined(AFS_HPUX100_ENV)
	    sdirEntry->d_off = origOffset;
#endif
	    AFS_UIOMOVE((char *)sdirEntry, sizeof(*sdirEntry), UIO_READ, auio,
			code);
	    if (code == 0)
		AFS_UIOMOVE(ode->name, o_slen, UIO_READ, auio, code);
	    /* pad out the remaining characters with zeros */
	    if (code == 0) {
		AFS_UIOMOVE(bufofzeros,
			    ((o_slen + 1 + DIRPAD) & ~DIRPAD) - o_slen,
			    UIO_READ, auio, code);
	    }
	    /* pad out the difference between rlen and slen... */
	    if (DIRSIZ_LEN(o_slen) < rlen) {
		while (DIRSIZ_LEN(o_slen) < rlen) {
		    int minLen = rlen - DIRSIZ_LEN(o_slen);
		    if (minLen > sizeof(bufofzeros))
			minLen = sizeof(bufofzeros);
		    AFS_UIOMOVE(bufofzeros, minLen, UIO_READ, auio, code);
		    rlen -= minLen;
		}
	    }
#else /* AFS_HPUX_ENV */
	    code = afs_readdir_move(ode, avc, auio, o_slen, len, origOffset);
#endif /* AFS_HPUX_ENV */
	}
#ifdef	AFS_SGI53_ENV
	len = use64BitDirent ? DIRENTSIZE(o_slen =
					  n_slen) : IRIX5_DIRENTSIZE(o_slen =
								     n_slen);
#else
	len = DIRSIZ_LEN(o_slen = n_slen);
#endif /* AFS_SGI53_ENV */
	
	DRelease(&oldEntry, 0);
	oldEntry = nextEntry;
	ode = nde;
	AFS_UIO_SETOFFSET(auio, (us + afs_dir_NameBlobs(nde->name)) << 5);
    }
    
    DRelease(&oldEntry, 0);

  dirend:
    ReleaseReadLock(&tdc->lock);
    afs_PutDCache(tdc);
    ReleaseReadLock(&avc->lock);

  done:
#ifdef	AFS_HPUX_ENV
    osi_FreeSmallSpace((char *)sdirEntry);
#endif
    AFS_DISCON_UNLOCK();
    afs_PutFakeStat(&fakestate);
    code = afs_CheckCode(code, treq, 28);
    afs_DestroyReq(treq);
    return code;
}

#endif /* !AFS_LINUX20_ENV */
