#ifndef lint 
#ident "@(#)hsfs_node.c 1.8 93/11/18" 
#endif

/*
 * Directory operations for High Sierra filesystem
 * Copyright (c) 1989 by Sun Microsystem, Inc.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ucred.h>
#include <sys/time.h>
#include <sys/buf.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <ufs/inode.h>
#include <sys/kernel.h>
#include <sys/errno.h>
#include <sys/dirent.h>

#include <hsfs/hsfs_spec.h>
#include <hsfs/hsfs_isospec.h>
#include <hsfs/hsfs_node.h>
#include <hsfs/hsfs_private.h>
#include <hsfs/hsfs_susp.h>
#include <hsfs/hsfs_rrip.h>

#include <vm/pvn.h>
#include <vm/seg.h>
#include <vm/seg_map.h>
#include <vm/seg_vn.h>
#include <vm/rm.h>
#include <vm/swap.h>
#include <machine/seg_kmem.h>


enum dirblock_result { FOUND_ENTRY, WENT_PAST, HIT_END };
static enum dirblock_result process_dirblock();

extern struct vnodeops hsfs_vnodeops;
extern char *strncpy();

static void hs_remhash();
static void hs_addfreeb();
static void hs_addfreef();

/*
 * hs_access
 * Return 0 if the desired access may be granted.
 * Otherwise return error code.
 */
int
hs_access(vp, m, cred)
	register struct vnode *vp;
	register int m;
	register struct ucred *cred;
{
	register int *gp;
	register struct hsnode *hp;

	if (m & VWRITE) {
		if (vp->v_vfsp->vfs_flag & VFS_RDONLY)
			return (EROFS);
	}
	if (cred->cr_uid == 0)
		return (0);		/* super-user always gets access */

	hp = VTOH(vp);

	/*
	 * XXX - For now, use volume protections.
	 *	 Also, always grant EXEC access for directories
	 *	 if READ access is granted.
	 */
	if ((vp->v_type == VDIR) && (m & VEXEC)) {
		m &= ~VEXEC;
		m |= VREAD;
	}
	if (cred->cr_uid != hp->hs_dirent.uid) {
		m >>= 3;
		/*
		 * Have to check groups manually since groupmember()
		 * checks uarea instead of a credentials list.
		 */
		if (cred->cr_gid == hp->hs_dirent.gid)
			goto found;
		for (gp = cred->cr_groups;
		    gp < &(cred->cr_groups[NGROUPS]) && *gp != NOGROUP;
		    gp++)
			if (*gp == hp->hs_dirent.gid)
				goto found;
		m >>= 3;
	}
found:
	if ((m & hp->hs_dirent.mode) == m)
		return (0);
	return (EACCES);
}

#if ((HS_HASHSIZE & (HS_HASHSIZE - 1)) == 0)
#define	HS_HASH(l)	((u_long)(l) & (HS_HASHSIZE - 1))
#else
#define	HS_HASH(l)	((u_long)(l) % HS_HASHSIZE)
#endif
#define	HS_HPASH(hp)	HS_HASH((hp)->hs_nodeid)

extern int physmem;

/*
 * initialize incore hsnode table size
 *
 */
struct hstable *
hs_inithstbl(vfsp)
	struct vfs *vfsp;
{
	register struct hstable *htp;
	int size;
	int i;
	int nohsnode;

	/* allocate incore hsnode space based on memory size */
	/* minimum 16 K for 4M machine, 64K for others */
	size = (physmem <= 512) ? HS_HSTABLESIZE : 4 * HS_HSTABLESIZE;

	htp = (struct hstable *)new_kmem_alloc((u_int)size, KMEM_SLEEP);
	htp -> hs_vfsp = vfsp;
	htp -> hs_tablesize = size;
	for (i = 0; i < HS_HASHSIZE; i++)
		htp->hshash[i] = NULL;
	htp->hsfree_f = NULL;
	htp->hsfree_b = NULL;
	htp->hs_refct = 0;
	htp->hs_nohsnode = nohsnode
		= (size - sizeof (struct hstable) + sizeof (struct hsnode))/
		sizeof (struct hsnode);

	for (i = 0; i < nohsnode; i++) {
		htp->hs_node[i].hs_dirent.ext_lbn = 0;
		htp->hs_node[i].hs_hash = NULL;
		htp->hs_node[i].hs_freef = NULL;
		htp->hs_node[i].hs_freeb = NULL;
		htp->hs_node[i].hs_dirent.sym_link = (char *)NULL;
		(void) hs_addfreeb(htp, &htp->hs_node[i]);
	}

	/*
	 * could initialize more stuff in this routine (e.g. vnode)
	 * do it next time...
	 */

	return (htp);
}

/*
 * initialize incore hsnode table size
 *
 */
void
hs_freehstbl(vfsp)
	struct vfs *vfsp;
{
	register struct hstable *htp;

	htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
	kmem_free((caddr_t) htp, (u_int) htp->hs_tablesize);
}

/*
 * Add an hsnode to the end of the free list.
 */
static void
hs_addfreeb(htp, hp)
	register struct hstable *htp;
	register struct hsnode *hp;
{
	register struct hsnode *ep;

	ep = htp->hsfree_b;
	htp->hsfree_b = hp;		/* hp is the last entry in free list */
	hp->hs_freef = NULL;
	hp->hs_freeb = ep;		/* point at previous last entry */
	if (ep == NULL)
		htp->hsfree_f = hp;	/* hp is only entry in free list */
	else
		ep->hs_freef = hp;	/* point previous last entry at hp */
}

/*
 * Add an hsnode to the front of the free list.
 */
static void
hs_addfreef(htp, hp)
	register struct hstable *htp;
	register struct hsnode *hp;
{
	register struct hsnode *ep;

	ep = htp->hsfree_f;
	htp->hsfree_f = hp;		/* hp is the first entry in free list */
	hp->hs_freeb = NULL;
	hp->hs_freef = ep;		/* point at previous last entry */
	if (ep == NULL)
		htp->hsfree_b = hp;	/* hp is only entry in free list */
	else
		ep->hs_freeb = hp;	/* point previous last entry at hp */
}

/*
 * Get an hsnode from the front of the free list.
 */
static struct hsnode *
hs_getfree(htp)
	register struct hstable *htp;
{
	register struct hsnode *hp;

	hp = htp->hsfree_f;
	if (hp != NULL) {
		htp->hsfree_f = hp->hs_freef;
		if (htp->hsfree_f != NULL)
			htp->hsfree_f->hs_freeb = NULL;
		else
			htp->hsfree_b = NULL;
	} else
		printf("hsfs: hsnode table full\n");
	return (hp);
}

/*
 * Remove an hsnode from the free list.
 */
static void
hs_remfree(htp, hp)
	register struct hstable *htp;
	register struct hsnode *hp;
{

	if (hp->hs_freef != NULL)
		hp->hs_freef->hs_freeb = hp->hs_freeb;
	else
		htp->hsfree_b = hp->hs_freeb;
	if (hp->hs_freeb != NULL)
		hp->hs_freeb->hs_freef = hp->hs_freef;
	else
		htp->hsfree_f = hp->hs_freef;
}

/*
 * Look for hsnode in hash list.
 * Check equality of fsid and starting block of data extent.
 * If found, reactivate it if inactive.
 */
struct vnode *
hs_findhash(nodeid, vfsp)
	u_long nodeid;
	struct vfs *vfsp;
{
	register struct hsnode *tp;
	register struct hstable *htp;

	htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
	for (tp = htp->hshash[HS_HASH(nodeid)]; tp != NULL; tp = tp->hs_hash) {
		if (tp->hs_nodeid == nodeid) {
			if ((HTOV(tp)->v_count)++ == 0) {
				/*
				 * reactivating a free hsnode:
				 * remove from free list
				 */
				hs_remfree(htp, tp);
				(htp->hs_refct)++;
			}
			return (HTOV(tp));
		}
	}
	return (NULL);
}

static void
hs_addhash(htp, hp)
	register struct hstable *htp;
	struct hsnode *hp;
{
	register u_int hashno;

	hashno = (u_int)HS_HPASH(hp);
	hp->hs_hash = htp->hshash[hashno];
	htp->hshash[hashno] = hp;
}

static void
hs_remhash(htp, hp)
	register struct hstable *htp;
	struct hsnode *hp;
{
	register struct hsnode **tp;

	for (tp = &htp->hshash[HS_HPASH(hp)];
		*tp != NULL; tp = &(*tp)->hs_hash) {
		if (*tp == hp) {
			struct vnode *vp;

			vp = HTOV(hp);
			/* file is no longer reference, destroy all old pages */
			if (vp->v_pages != NULL)
				/*  pvn_vplist_dirty will abort all old pages */
				(void) pvn_vplist_dirty(vp, 0, B_INVAL);
			*tp = hp->hs_hash;
			break;
		}
	}
}

/* destroy all old pages */
void
hs_synchash(vfsp)
	struct vfs *vfsp;
{
	register struct hstable *htp;
	register int i;
	register struct hsnode *hp;

	htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
	for (i = 0; i < HS_HASHSIZE; i++) {
		for (hp = htp->hshash[i]; hp != NULL; hp = hp->hs_hash) {
			if ((HTOV(hp))->v_pages != NULL)
				(void) pvn_vplist_dirty((HTOV(hp)), 0, B_INVAL);
		}
	}

	/* now destroy all sym_link buffers */
	for (i = 0; i < HS_HASHSIZE; i++) {
		for (hp = htp->hshash[i]; hp != NULL; hp = hp->hs_hash) {
			if (hp->hs_dirent.sym_link != (char *)NULL) {
				kmem_free((caddr_t)hp->hs_dirent.sym_link,
				(u_int) SYM_LINK_LEN(hp->hs_dirent.sym_link));
				hp->hs_dirent.sym_link = (char *)NULL;
			}
		}
	}
}

/*
 * hs_makenode
 *
 * Construct an hsnode.
 * Caller specifies the directory entry, the block number and offset
 * of the directory entry, and the vfs pointer.
 * note: off is the sector offset, not lbn offset
 * if NULL is returned implies file system hsnode table full
 */
struct vnode *
hs_makenode(dp, lbn, off, vfsp)
	struct hs_direntry *dp;
	u_int lbn;
	u_int off;
	struct vfs *vfsp;
{
	register struct hsnode *hp;
	register struct vnode *vp;
	register struct hstable *htp;
	struct vnode *newvp;
	u_long nodeid;


	htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
  
  	/*
  	 * Construct the nodeid: in the case of a directory
  	 * entry, this should point to the canonical dirent, the "."
  	 * directory entry for the directory.  This dirent is pointed
  	 * to by all directory entries for that dir (including the ".")
  	 * entry itself.
  	 * In the case of a file, simply point to the dirent for that
  	 * file (there are no hard links in Rock Ridge, so there's no
  	 * need to determine what the canonical dirent is.
  	 */
  	if (dp->type == VDIR) {
  		lbn = dp->ext_lbn;
  		off = 0;
  	}
  	nodeid = MAKE_NODEID(lbn, off, vfsp);
  
	/* look for hsnode in cache first */
	if ((vp = hs_findhash(nodeid, vfsp)) == NULL) {
		/*
		 * not in cache, get one off freelist or else allocate one
		 */
		if ((hp = hs_getfree(htp)) != NULL) {
			hs_remhash(htp, hp);
		} else {
			return (NULL);
		}

		if (hp->hs_dirent.sym_link != (char *)NULL)
			kmem_free(hp->hs_dirent.sym_link,
				(u_int) SYM_LINK_LEN(hp->hs_dirent.sym_link));

		bzero((caddr_t)hp, sizeof (*hp));
		bcopy((caddr_t)dp, (caddr_t)&hp->hs_dirent, sizeof (*dp));
		dp->sym_link = (char *)NULL;
		hp->hs_dir_lbn = lbn;
		hp->hs_dir_off = off;
		hp->hs_nodeid = nodeid;
		hp->hs_offset = 0;
		if (off > HS_SECTOR_SIZE)
			printf("hs_makenode: bad offset\n");

		/* initialize for VDIR */
		hp->hs_ptbl_idx = NULL;
		vp = HTOV(hp);
		VN_INIT(vp, vfsp, dp->type, dp->r_dev);
		vp->v_op = &hsfs_vnodeops;
		vp->v_data = (caddr_t)hp;


		/*
		* if it's a device, call specvp
		*/
		if (ISVDEV(vp->v_type)) {
			newvp = specvp(vp, vp->v_rdev, vp->v_type);
			VN_RELE(vp);
			vp = newvp;
			goto end;
		}

		hs_addhash(htp, hp);
		(htp->hs_refct)++;
	}

end:
	if (dp->sym_link != (char *)NULL) {
		kmem_free(dp->sym_link,
			(u_int) SYM_LINK_LEN(dp->sym_link));
		dp->sym_link = (char *)NULL;
	}
	return (vp);
}

/*
 * hs_freenode
 *
 * Deactivate an hsnode.
 * Leave it on the hash list but put it on the free list.
 * if the vnode does not have any pages, put in front of free list
 * else put in back of the free list
 *
 */
void
hs_freenode(hp, vfsp, nopage)
	struct hsnode *hp;
	struct vfs *vfsp;
	int nopage;		/* 1 if no page, 0 otherwise */
{
	register struct hstable *htp;

	htp = ((struct hsfs *)VFS_TO_HSFS(vfsp))->hsfs_hstbl;
	if (nopage)
		hs_addfreef(htp, hp); /* add to front of free list */
	else
		hs_addfreeb(htp, hp); /* add to back of free list */
	(htp->hs_refct)--;
}

/*
 * hs_remakenode
 *
 * Reconstruct a vnode given the location of its directory entry.
 * Caller specifies the the block number and offset
 * of the directory entry, and the vfs pointer.
 * Returns an error code or 0.
 */
int
hs_remakenode(lbn, off, vfsp, vpp)
	u_int lbn;
	u_int off;
	struct vfs *vfsp;
	struct vnode **vpp;
{
	register struct hsfs *fsp;
	register u_int secno;
	u_char *dirp;
	struct hs_direntry hd;
	int error;
	u_char *buffer;

	buffer = (u_char *)new_kmem_alloc((size_t)HS_SECTOR_SIZE, KMEM_SLEEP);

	if (buffer == (u_char *)NULL)
		return (ENOMEM);

	bzero ((caddr_t) &hd, sizeof (hd));

	/* Convert to sector and offset */
	fsp = VFS_TO_HSFS(vfsp);
	if (off > HS_SECTOR_SIZE) {
		printf("hs_remakenode: bad offset\n");
		error = EINVAL;
		goto end;
	}
	secno = LBN_TO_SEC(lbn, vfsp);
	dirp = buffer;

	/* Read sector and parse directory entry */
	if (error = hs_readsector(fsp->hsfs_devvp, secno, dirp))
		goto end;

	error = hs_parsedir(fsp, &dirp[off], &hd, (char *)NULL, (int *)NULL);

	if (!error) {
		*vpp = hs_makenode(&hd, lbn, off, vfsp);
		if (*vpp == NULL)
			error = ENFILE;
	}

end:
	(void) kmem_free((caddr_t)buffer, (size_t)HS_SECTOR_SIZE);
	return (error);
}


/*
 * hs_dirlook
 *
 * Look for a given name in a given directory.
 * If found, construct an hsnode for it.
 */
int
hs_dirlook(dvp, name, namlen, vpp, cred)
	struct vnode *dvp;
	register char *name;
	int namlen;			/* length of 'name' */
	struct vnode **vpp;
	struct ucred *cred;
{
	register struct hsnode *dhp;
	struct hsfs *fsp;
	int error = 0;
	u_int offset;			/* real offset in directory */
	u_int last_offset;		/* last index into current dir block */
	char *cmpname;			/* case-folded name */
	int cmpnamelen;
	int adhoc_search;		/* did we start at begin of dir? */
	int end;
	u_int hsoffset;
	struct fbuf *fbp;
	int bytes_wanted;
	int dirsiz;
	int is_rrip;

	if (error = hs_access(dvp, (int) VEXEC, cred))
		return (error);

	cmpname = (char *)new_kmem_alloc(MAXNAMELEN + 1, KMEM_SLEEP);
	if (cmpname == (char *)NULL)
		return (ENOMEM);

	dhp = VTOH(dvp);
	fsp = VFS_TO_HSFS(dvp->v_vfsp);

	is_rrip = IS_RRIP_IMPLEMENTED(fsp);

	/*
	 * For the purposes of comparing the name against dir entries,
	 * fold it to upper case.
	 */
	if (is_rrip) {
		(void) strcpy(cmpname, name);
		cmpnamelen = namlen;
	} else {
		cmpnamelen = hs_uppercase_copy(name, cmpname, namlen);
	}

	/* make sure dirent is filled up with all info */
	if (dhp->hs_dirent.ext_size == 0)
		hs_filldirent(dvp, &dhp->hs_dirent);

	/*
	 * No lock is needed - hs_offset is used as starting
	 * point for searching the directory.
	 */
	offset = dhp->hs_offset;
	hsoffset = offset;
	adhoc_search = (offset != 0);

	end = dhp->hs_dirent.ext_size;
	dirsiz = end;

tryagain:

	while (offset < end) {

		if ((offset & MAXBMASK) + MAXBSIZE > dirsiz)
			bytes_wanted = dirsiz - (offset & MAXBMASK);
		else
			bytes_wanted = MAXBSIZE;

		error = fbread(dvp, (offset & MAXBMASK),
			(unsigned int) bytes_wanted, S_READ, &fbp);
		if (error) {
			fbrelse(fbp, S_READ);
			goto done;
		}
		last_offset = (offset & MAXBMASK) + fbp->fb_count - 1;

#define	rel_offset(offset) ((offset) & MAXBOFFSET)  /* index into cur blk */

		switch (process_dirblock((u_char *)fbp->fb_addr, &offset,
					last_offset, cmpname,
					cmpnamelen, fsp, dhp, dvp,
					vpp, &error, is_rrip)) {
		case FOUND_ENTRY:
			/* found an entry, either correct or not */
			fbrelse(fbp, S_READ);
			goto done;

		case WENT_PAST:
			/*
			 * If we get here we know we didn't find it on the
			 * first pass. If adhoc_search, then we started a
			 * bit into the dir, and need to wrap around and
			 * search the first entries.  If not, then we started
			 * at the beginning and didn't find it.
			 */
			fbrelse(fbp, S_READ);
			if (adhoc_search) {
				offset = 0;
				end = hsoffset;
				adhoc_search = 0;
				goto tryagain;
			} else {
				error = ENOENT;
				goto done;
			}
			/* NOTREACHED */
			break;

		case HIT_END:
			fbrelse(fbp, S_READ);
			goto tryagain;
		}
	}
	/*
	 * End of all dir blocks, didn't find entry.
	 */
	if (adhoc_search) {
		offset = 0;
		end = hsoffset;
		adhoc_search = 0;
		goto tryagain;
	}
	error = ENOENT;
done:
	kmem_free(cmpname, MAXNAMELEN + 1);
	return (error);
}


/*
 * hs_parsedir
 *
 * Parse a Directory Record into an hs_direntry structure.
 * High Sierra and ISO directory are almost the same
 * except the flag and date
 */
int
hs_parsedir(fsp, dirp, hdp, dnp, dnlen)
	struct hsfs *fsp;
	u_char *dirp;
	struct hs_direntry *hdp;
	char *dnp;
	int *dnlen;
{
	u_char flags;
	int namelen, error;
	int name_change_flag = 0;	/* set if name was gotten in SUA */

	hdp->ext_lbn = HDE_EXT_LBN(dirp);
	hdp->ext_size = HDE_EXT_SIZE(dirp);
	hdp->xar_len = HDE_XAR_LEN(dirp);
	hdp->intlf_sz = HDE_INTRLV_SIZE(dirp);
	hdp->intlf_sk = HDE_INTRLV_SKIP(dirp);
	if (fsp->hsfs_vol_type == HS_VOL_TYPE_HS) {
		flags = HDE_FLAGS(dirp);
		(void) hs_parse_dirdate(HS_VOL_TYPE_HS,
			HDE_cdate(dirp), &hdp->cdate);
		(void) hs_parse_dirdate(HS_VOL_TYPE_HS,
			HDE_cdate(dirp), &hdp->adate);
		(void) hs_parse_dirdate(HS_VOL_TYPE_HS,
			HDE_cdate(dirp), &hdp->mdate);
		if (HDE_REGULAR_FILE(flags)) {
			hdp->type = VREG;
			hdp->mode = IFREG;
			hdp->nlink = 1;
		} else if (HDE_REGULAR_DIR(flags)) {
			hdp->type = VDIR;
			hdp->mode = IFDIR;
			hdp->nlink = 2;
		} else {
			printf("hsfs: filetype (0x%x) not supported\n", flags);
			return (EINVAL);
		}
		hdp->uid = fsp -> hsfs_vol.vol_uid;
		hdp->gid = fsp -> hsfs_vol.vol_gid;
		hdp->mode = hdp-> mode | (fsp -> hsfs_vol.vol_prot & 0777);
	} else if (fsp->hsfs_vol_type == HS_VOL_TYPE_ISO) {
		flags = IDE_FLAGS(dirp);
		(void) hs_parse_dirdate(HS_VOL_TYPE_ISO,
			IDE_cdate(dirp), &hdp->cdate);
		(void) hs_parse_dirdate(HS_VOL_TYPE_ISO,
			IDE_cdate(dirp), &hdp->adate);
		(void) hs_parse_dirdate(HS_VOL_TYPE_ISO,
			IDE_cdate(dirp), &hdp->mdate);
		if (HDE_REGULAR_FILE(flags)) {
			hdp->type = VREG;
			hdp->mode = IFREG;
			hdp->nlink = 1;
		} else if (HDE_REGULAR_DIR(flags)) {
			hdp->type = VDIR;
			hdp->mode = IFDIR;
			hdp->nlink = 2;
		} else {
			printf("hsfs: filetype (0x%x) not supported\n", flags);
			return (EINVAL);
		}
		hdp->uid = fsp -> hsfs_vol.vol_uid;
		hdp->gid = fsp -> hsfs_vol.vol_gid;
		hdp->mode = hdp-> mode | (fsp -> hsfs_vol.vol_prot & 0777);
                /*
		* Having this all filled in, let's see if we have any
		* SUA susp to look at.
		*/
		if (IS_SUSP_IMPLEMENTED(fsp)) {
			error = parse_sua((u_char *)dnp, dnlen,
					&name_change_flag, dirp, hdp, fsp,
					(u_char *)NULL, NULL);
			if (error) {
				if (hdp->sym_link) {
					kmem_free((caddr_t)hdp->sym_link,
					(u_int) SYM_LINK_LEN(hdp->sym_link));
					hdp->sym_link = (char *)NULL;
				}
				return (error);
			}
		}

	}
	hdp->xar_prot = (HDE_PROTECTION & flags) != 0;

#if dontskip
	if (hdp->xar_len > 0) {
		printf("hsfs: extended attributes not supported\n");
		return (EINVAL);
	}
#endif

	/* check interleaf size and skip factor */
	/* must both be zero or non-zero */
	if (hdp->intlf_sz + hdp->intlf_sk) {
		if ((hdp->intlf_sz == 0) || (hdp->intlf_sk == 0)) {
			printf("hsfs: interleaf size or skip factor error\n");
			return (EINVAL);
		}
		if (hdp->ext_size == 0) {
			printf("hsfs:");
			printf(" interleaving specified on zero length file\n");
			return (EINVAL);
		}
	}

	if (HDE_VOL_SET(dirp) != 1) {
		if (fsp->hsfs_vol.vol_set_size != 1 &&
		    fsp->hsfs_vol.vol_set_size != HDE_VOL_SET(dirp)) {
			printf("hsfs: multivolume file?\n");
			return (EINVAL);
		}
	}

        /*
	* If the name changed, then the NM field for RRIP was hit and
	* we should not copy the name again, just return.
	*/
	if (NAME_HAS_CHANGED(name_change_flag))
		return (0);



	/* return the pointer to the directory name and its length */
	if (dnp != NULL)
		namelen = hs_namecopy((char *) HDE_name(dirp), dnp,
			(int) HDE_NAME_LEN(dirp));
	else
		namelen = (int) HDE_NAME_LEN(dirp);
	if (dnlen != NULL)
		*dnlen = namelen;

	return (0);
}

/*
 * hs_namecopy
 *
 * Parse a file/directory name into UNIX form.
 * Delete trailing blanks, upper-to-lower case, add NULL terminator.
 * Returns the (possibly new) length.
 */
int
hs_namecopy(from, to, size)
	char *from;
	char *to;
	int size;
{
	register u_int i;
	register u_char c;
	register int lastspace;

	/* special handling for '.' and '..' */
	if (size == 1) {
		if (*from == '\0') {
			*to++ = '.';
			*to = '\0';
			return (1);
		} else if (*from == '\1') {
			*to++ = '.';
			*to++ = '.';
			*to = '\0';
			return (2);
		}
	}

	for (i = 0, lastspace = -1; i < size; i++) {
		c = from[i] & 0x7f;
		if (c == ';')
			break;
		if (c <= ' ') {
			if (lastspace == -1)
				lastspace = i;
		} else
			lastspace = -1;
		if ((c >= 'A') && (c <= 'Z'))
			c += 'a' - 'A';
		to[i] = c;
	}
	if (lastspace != -1)
		i = lastspace;
	to[i] = '\0';
	return (i);
}

/*
 * hs_uppercase_copy
 *
 * Convert a UNIX-style name into its HSFS equivalent.
 * Map to upper case.
 * Returns the (possibly new) length.
 */
hs_uppercase_copy(from, to, size)
	char *from;
	char *to;
	int size;
{
	register u_int i;
	register u_char c;

	/* special handling for '.' and '..' */

	if (size == 1 && *from == '.') {
		*to = '\0';
		return (1);
	} else if (size == 2 && *from == '.' && *(from+1) == '.') {
		*to = '\1';
		return (1);
	}

	for (i = 0; i < size; i++) {
		c = *from++;
		if ((c >= 'a') && (c <= 'z'))
			c = c - 'a' + 'A';
		*to++ = c;
	}
	return (size);
}

void
hs_filldirent(vp, hdp)
	struct vnode *vp;
	struct hs_direntry *hdp;
{
	u_int 	secno;
	u_int	secoff;
	struct vnode *realvp;
	struct hsfs *fsp;
	u_char *secp;
	u_char *buffer;

	if (vp->v_type != VDIR) {
		printf("hsfs_filldirent: vp (0x%x) not a directory", vp);
		return;
	}

	buffer = (u_char *)new_kmem_alloc((size_t)HS_SECTOR_SIZE, KMEM_SLEEP);

	if (buffer == (u_char *)NULL)
		return;

	secp = buffer;
	fsp = VFS_TO_HSFS(vp ->v_vfsp);
	realvp = fsp -> hsfs_devvp;
	secno = LBN_TO_SEC(hdp->ext_lbn+hdp->xar_len, vp->v_vfsp);
	secoff = LBN_TO_BYTE(hdp->ext_lbn+hdp->xar_len, vp->v_vfsp) &
			MAXHSOFFSET;
	if (hs_readsector(realvp, secno, secp))
		goto end;

	/* quick check */
	if (hdp->ext_lbn != HDE_EXT_LBN(&secp[secoff])) {
		printf("hsfs_filldirent: dirent not match\n");
		/* keep on going */
	}
	(void) hs_parsedir(fsp, &secp[secoff], hdp, (char *)NULL, (int *)NULL);

end:
	(void) kmem_free((caddr_t)buffer, (size_t)HS_SECTOR_SIZE);
	return;
}



/*
 * Look through a directory block for a matching entry.
 */
static enum dirblock_result
process_dirblock(blkp, offset, last_offset, nm, nmlen, fsp, dhp, dvp, vpp,
		error, is_rrip)
	u_char *blkp;		/* dir block */
	u_int *offset;		/* lower index */
	u_int last_offset;	/* upper index */
	char *nm;		/* name to compare against */
	int nmlen;		/* length of name */
	struct hsfs *fsp;
	struct hsnode *dhp;
	struct vnode *dvp;
	struct vnode **vpp;
	int *error;		/* return value: errno */
	int is_rrip;		/* 1 if rock ridge is implemented */
{
	char *dname;		/* name in directory entry */
	char *cmpname;		/* name for comparison */
	int dnamelen;		/* length of name */
	int cmpnamelen;		/* length of name for comparison */
	struct hs_direntry hd;
	int hdlen;
	register u_char *dirp;	/* the directory entry */
	int res, parsedir_res;
	char	*rrip_name_str;
	char	*rrip_tmp_name;
	enum dirblock_result	err;

	if (is_rrip) {
		rrip_name_str = (char *)new_kmem_alloc(MAXNAMELEN + 1,
								KMEM_SLEEP);
		if (rrip_name_str == (char *)NULL) {
			/*
			 * XXX we really should return an error indication
			 * here
			 */
			return (HIT_END);
		}
		rrip_tmp_name = (char *)new_kmem_alloc(MAXNAMELEN + 1,
								KMEM_SLEEP);
		if (rrip_tmp_name == (char *)NULL) {
			/*
			 * XXX we really should return an  error indication
			 * here
			 */
			kmem_free(rrip_name_str, MAXNAMELEN + 1);
			return (HIT_END);
		}

		rrip_name_str[0] = '\0';
		rrip_tmp_name[0] = '\0';
	}


        cmpname = (char *)new_kmem_alloc(MAXNAMELEN + 1, KMEM_SLEEP);
	if (cmpname == (char *)NULL) {
		/*
		 * XXX we really should return an  error indication
		 * here
		 */
		err = HIT_END;
		goto do_ret;
	}

	while (*offset < last_offset) {

		/*
		 * Directory Entries cannot span sectors.
		 * Unused bytes at the end of each sector are zeroed.
		 * Therefore, detect this condition when the size
		 * field of the directory entry is zero.
		 */
		hdlen = (int)((u_char)
				HDE_DIR_LEN(&blkp[rel_offset(*offset)]));
		if (hdlen == 0) {
			/* advance to next sector boundary */
			*offset = (*offset & MAXHSMASK) + HS_SECTOR_SIZE;

			if (*offset > last_offset) {
				err = HIT_END;	/* end of block */
				goto do_ret;
			} else
				continue;
		}

		/*
		 * Zero out the hd to read new directory
		 */
		bzero((caddr_t)&hd, sizeof (hd));

		/*
		 * Just ignore invalid directory entries.
		 * XXX - maybe hs_parsedir() will detect EXISTENCE bit
		 */
		dirp = &blkp[rel_offset(*offset)];
		dname = (char *)HDE_name(dirp);
		dnamelen = (int) ((u_char) HDE_NAME_LEN(dirp));

		/*
		 * If the rock ridge is implemented, then we copy the name
		 * from the SUA area to rrip_name_str. If no Alternate
		 * name is found, then use the uppercase NM in the
		 * rrip_name_str char array.
		 */
		if (is_rrip) {
			int rr_namelen;

			rrip_name_str[0] = '\0';
			rr_namelen = rrip_namecopy(nm, &rrip_name_str[0],
					&rrip_tmp_name[0], dirp, fsp, &hd);
			if (hd.sym_link != (char *)NULL) {
				kmem_free(hd.sym_link,
					(u_int) SYM_LINK_LEN(hd.sym_link));
				hd.sym_link = (char *)NULL;
			}
			if (rr_namelen == -1){ /* use iso name instead */
				register int i;
				for (i = dnamelen; (dname[i] != ';') &&
						(i >= 0); i--);
				if (dname[i] == ';')
					dnamelen = i;
			} else{
				dname = (char *)&rrip_name_str[0];
				dnamelen = rr_namelen;
			}
		} else {
			register int i;
			/*
			 * make sure that we get rid of ';' in the dname of
			 * an iso direntry, as we should have no knowledge
			 * of file versions.
			 */
			for (i = dnamelen; (dname[i] != ';') && (i >= 0); i--)
				;

			if (dname[i] == ';')
				dnamelen = i;
		}


		/*
                 * For the purposes of comparing the name against dir entries,
                 * fold it to upper case.
                 */
                if (is_rrip) {
                        (void) strcpy(cmpname, dname);
                        cmpnamelen = dnamelen;
                } else {
                        cmpnamelen = hs_uppercase_copy(dname, cmpname, dnamelen);
                }
 
#ifdef MAB
                /*
		 * Quickly screen for a non-matching entry, but not for RRIP.
		 */
		if (!is_rrip && *nm < *cmpname) {
			err = WENT_PAST;
			goto do_ret;
		}
#endif MAB

		if (*nm != *cmpname || nmlen != cmpnamelen) {
			/* look at next dir entry */
			RESTORE_NM(rrip_tmp_name, nm);
			*offset += hdlen;
			continue;
		}

		if ((res = nmcmp(cmpname, nm, nmlen)) == 0) {
			/* name matches */
			parsedir_res =
				hs_parsedir(fsp, dirp, &hd, (char *)NULL,
					    (int *) NULL);
			if (!parsedir_res) {
				u_int lbn;	/* logical block number */
				u_int secoff;

				lbn = dhp->hs_dirent.ext_lbn +
					dhp->hs_dirent.xar_len +
						(*offset/
						fsp->hsfs_vol.lbn_size);
				secoff = *offset & MAXHSOFFSET;
				*vpp = hs_makenode(&hd, lbn,
						secoff, dvp->v_vfsp);
				if (*vpp == NULL) {
					*error = ENFILE;
					RESTORE_NM(rrip_tmp_name, nm);
					err = FOUND_ENTRY;
					goto do_ret;
				}
				dhp->hs_offset = *offset;
				RESTORE_NM(rrip_tmp_name, nm);
				err = FOUND_ENTRY;
				goto do_ret;
			} else {
				/* improper dir entry */
				*error = parsedir_res;
				RESTORE_NM(rrip_tmp_name, nm);
				err = FOUND_ENTRY;
				goto do_ret;
			}
		} else if (!is_rrip && res < 0) {
			/* name < dir entry */
			err = WENT_PAST;
			goto do_ret;
		}
		/*
		 * name > dir entry,
		 * look at next one.
		 */
		*offset += hdlen;
	}
	RESTORE_NM(rrip_tmp_name, nm);
	err = HIT_END;

do_ret:
	kmem_free(cmpname, MAXNAMELEN + 1);
	if (is_rrip) {
		kmem_free(rrip_name_str, MAXNAMELEN + 1);
		kmem_free(rrip_tmp_name, MAXNAMELEN + 1);
	}
	return (err);
}


/*
 * Compare the names, returning < 0 if a < b,
 * 0 if a == b, and > 0 if a > b.
 */
static int
nmcmp(a, b, len)
	register char *a;
	register char *b;
	register int len;
{
	while (len--) {
		if (*a == *b) {
			b++; a++;
		} else {
			/* if file version, stop */
			if ((*a == ';') && (*b == '\0'))
				return (0);
			return ((u_char)*b - (u_char)*a);
		}
	}
	return (0);
}
