/*
 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*	@(#)hfs_vnodeops.c	3.0
 *
*	(c) 1997-1999	Apple Computer, Inc.  All Rights Reserved
 *	(c) 1990, 1992 NeXT Computer, Inc.  All Rights Reserved
 *	
 *
 *	hfs_vnodeops.c -- vnode layer for loadable Macintosh file system
 *
 *	MODIFICATION HISTORY:
 *	 	11-Nov-1999	Scott Roberts	Does not update if times have not changed (#2409116)
 *	 	 9-Nov-1999	Scott Roberts	Added cluster_close to hfs_fsync(#2398208)
 *       9-Nov-1999	Don Brady	Fix locking bug in hfs_close [#2399157].
 *		15-Sep-1999	Pat Dirks	Changed hfs_setattrlist to allow changing flags on plain-HFS volumes w/o ownership [#2365108];
 *								Changed to use hfs_write_access instead of obsolete hfs_writepermission uniformly throughout.
 *       7-Sep-1999	Don Brady	Add HFS Plus hard-link support.
 *		 8-Sep-1999		Pat Dirks	Changed hfs_rename to change mod. date on parent directory [#2297825].
 *		26-Aug-1999		Pat Dirks	Changed hfs_chflags to allow locking on HFS volumes w. write access only as workaround [#2313439].
 *       2-Sep-1999     Pat Dirks       Fixed hfs_pathconf to return same info for hfs/hfs+ for max. name length [#2382208]
 *	26-Aug-1999	Pat Dirks	Changed hfs_chflags to allow locking on HFS volumes w. write access only as workaround [#2313439].
*       24-Jul-1999 	Earsh Nandkeshwar	Rewrote readdirattr.
*	15-Jul-1999	Pat Dirks	Fixed hfs_readdir to return EINVAL if design assumption of uio->uio_iovcnt == 1 is violated
 *								and cleaned up call to uiomove to check space available first.
 *       2-Jul-1999     Pat Dirks       Fixed hfs_setattrlist to ignore attempts to set null volume name (#2331829).
 *      18-May-1999	Don Brady		Add support for rooting from HFS Plus.
 *	 4-May-1999	Don Brady		Split off hfs_search.c
 *      15-Apr-1999	Don Brady		Change va_nlink back to 1 for directories in hfs_getattr.
 *       6-Apr-1999	Don Brady		Fix deference of NULL h_sibling in hfs_chid.
 *  29-Mar-1999 Scott Roberts	Put in the correct . and .. entries for readdir
 *  22-Mar-1999 Don Brady		Add UFS delete semantic support to hfs_remove.
 *       1-Mar-1999	Scott Roberts	h_meta is now released when the complex vnode is relesed
 *      26-Feb-1999	Pat Dirks (copied by Chw) Fixed hfs_lookup to check for
 *                                error return on vget.
 *      25-Feb-1999     Pat Dirks       Fixed hfs_remove to use a local copy of the h_sibling pointer around vnode_uncache.
 *	 3-Feb-1999	Pat Dirks		Changed to stop updating wrapper volume name in MDB since wrapper volume's
 *								catalog isn't updated and this inconsistency trips Disk First Aid's checks.
 *	22-Jan-1999	Pat Dirks		Changed hfs_rename, hfs_remove, and hfs_rmdir to call cache_purge.
 *	22-Jan-1999	Don Brady		After calling hfsMoveRename call hfs_getcatalog to get new name.
 *	12-Jan-1999	Don Brady		Fixed the size of ATTR_CMN_NAME buffer to NAME_MAX + 1.
 *	 8-Jan-1999	Pat Dirks		Added hfs_writepermission and change hfs_setattrlist to use it instead of
 *								including an incorrect derivative of hfs_access in-line.
 *	15-Dec-1998 Pat Dirks		Changed setattrlist to do permission checking as appropriate (Radar #2290212).
 *	17-Nov-1998 Scott Roberts	Added support for long volume names in SetAttrList().
 *	6-Nov-1998 Don Brady		Add support for UTF-8 names.
 *	 3-Nov-1998	Umesh Vaishampayan	Changes to deal with "struct timespec"
 *						change in the kernel.	
 *  21-Oct-1998 Scott Roberts	Added support for advisory locking (Radar #2237914).
 *  25-Sep-1998 Don Brady		Changed hfs_exchange to call hfs_chid after updating catalog (radar #2276605).
 *	23-Sep-1998 Don Brady		hfs_setattrlist now calls hfs_chown and hfs_chmod to change values.
 *	15-Sep-1998 Pat Dirks		Cleaned up vnode unlocking on various error exit paths and changed
 *								to use new error stub routines in place of hfs_mknod and hfs_link.
 *  16-Sep-1998	Don Brady		When renaming a volume in hfs_setattrlist, also update hfs+ wrapper name (radar #2272925).
 *   1-Sep-1998	Don Brady		Fix uninitiazed time variable in hfs_makenode (radar #2270372).
 *  31-Aug-1998	Don Brady		Adjust change time for DST in hfs_update (radar #2265075).
 *  12-Aug-1998	Don Brady		Update complex node name in hfs_rename (radar #2262111).
 *   5-Aug-1998	Don Brady		In hfs_setattrlist call MacToVFSError after calling UpdateCatalogNode (radar #2261247).
 *  21-Jul-1998	Don Brady		Fixed broken preflight in hfs_getattrlist.
 *      17-Jul-1998	Clark Warner		Fixed the one left out case of freeing M_NAMEI in hfs_abort
 *	13-Jul-1998	Don Brady		Add uio_resid preflight check to hfs_search (radar #2251855).
 *	30-Jun-1998	Scott Roberts	        Changed hfs_makenode and its callers to free M_NAMEI.
 *	29-Jun-1998	Don Brady		Fix unpacking order in UnpackSearchAttributeBlock (radar #2249248).
 *	13-Jun-1998	Scott Roberts		Integrated changes to hfs_lock (radar #2237243).
 *	 4-Jun-1998	Pat Dirks		Split off hfs_lookup.c and hfs_readwrite.c
 *	 3-Jun-1998	Don Brady		Fix hfs_rename bugs (radar #2229259, #2239823, 2231108 and #2237380).
 *								Removed extra vputs in hfs_rmdir (radar #2240309).
 *	28-May-1998	Don Brady		Fix hfs_truncate to correctly extend files (radar #2237242).
 *	20-May-1998	Don Brady		In hfs_close shrink the peof to the smallest size neccessary (radar #2230094).
 *	 5-May-1998	Don Brady		Fixed typo in hfs_rename (apply H_FILEID macro to VTOH result).
 *	29-Apr-1998	Joe Sokol		Don't do cluster I/O when logical block size is not 4K multiple.
 *	28-Apr-1998	Pat Dirks		Cleaned up unused variable physBlockNo in hfs_write.
 *	28-Apr-1998	Joe Sokol		Touched up support for cluster_read/cluster_write and enabled it.
 *	27-Apr-1998	Don Brady		Remove some DEBUG_BREAK calls in DbgVopTest.
 *	24-Apr-1998	Pat Dirks		Fixed read logic to read-ahead only ONE block, and of only logBlockSize instead of 64K...
 *								Added calls to brelse() on errors from bread[n]().
 *								Changed logic to add overall length field to AttrBlockSize only on attribute return operations.
 *	23-Apr-1998	Don Brady		The hfs_symlink call is only supported on HFS Plus disks.
 *	23-Apr-1998	Deric Horn		Fixed hfs_search bug where matches were skipped when buffer was full.
 *	22-Apr-1998	Scott Roberts		Return on error if catalog mgr returns an error in truncate.
 *	21-Apr-1998	Don Brady		Fix up time/date conversions.
 *	20-Apr-1998	Don Brady		Remove course-grained hfs metadata locking.
 *	17-Apr-1998	Pat Dirks		Officially enabled searchfs in vops table.
 *	17-Apr-1998	Deric Horn		Bug fixes to hfs_search, reenabled searchfs trap for upcoming kernel build.
 *	15-Apr-1998	Don Brady		Add locking for HFS B-trees. Don't lock file meta lock for VSYSTEM files.
 *								Don't call VOP_UPDATE for system files. Roll set_time into hfs_update.
 *	14-Apr-1998	Pat Dirks		Cleaned up fsync to skip complex nodes and not hit sibling nodes.
 *	14-Apr-1998	Deric Horn		Added hfs_search() and related routines for searchfs() support.
 *	14-Apr-1998	Scott Roberts		Fixed paramaters to ExchangeFileIDs()
 *	13-Apr-1998	Pat Dirks		Changed to update H_HINT whenever hfs_getcatalog was called.
 *	 8-Apr-1998	Pat Dirks		Added page-in and page-out passthrough routines to keep MapFS happy.
 *	 6-Apr-1998	Pat Dirks		Changed hfs_write to clean up code and fix bug that caused
 *								zeroes to be interspersed in data.  Added debug printf to hfs_read.
 *	 6-Apr-1998	Scott Roberts		Added complex file support.
 *	02-apr-1998	Don Brady		UpdateCatalogNode now takes parID and name as input.
 *	31-mar-1998	Don Brady		Sync up with final HFSVolumes.h header file.
 *	27-mar-1998	Don Brady		Check result from UFSToHFSStr to make sure hfs/hfs+ names are not greater than 31 characters.
 *	27-mar-1998	chw			minor link fixes.
 *	19-Mar-1998	ser			Added hfs_readdirattr.
 *	17-Mar-1998	ser			Removed CheckUserAccess. Added code to implement ExchangeFileIDs
 *	16-Mar-1998	Pat Dirks		Fixed logic in hfs_read to properly account for space
 *								remaining past selected offset and avoid premature panic.
 *	16-jun-1997	Scott Roberts
 *	   Dec-1991	Kevin Wells at NeXT:
 *			Significantly modified for Macintosh file system.
 *			Added support for NFS exportability.
 *	25-Jun-1990	Doug Mitchell at NeXT:
 *			Created (for DOS file system).
 */

#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/dirent.h>
#include <sys/stat.h>
#include <sys/buf.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/namei.h>
#include <sys/attr.h>
#include <sys/ubc.h>
#include <sys/utfconv.h>
#include <miscfs/specfs/specdev.h>
#include <miscfs/fifofs/fifo.h>

#include <machine/spl.h>

#include <sys/kdebug.h>

#include	"hfs.h"
#include	"hfs_lockf.h"
#include	"hfs_dbg.h"
#include	"hfs_mount.h"

#include "hfscommon/headers/CatalogPrivate.h"
#include "hfscommon/headers/BTreesInternal.h"
#include "hfscommon/headers/FileMgrInternal.h"
#include "hfscommon/headers/HFSUnicodeWrappers.h"

#define OWNERSHIP_ONLY_ATTRS (ATTR_CMN_OWNERID | ATTR_CMN_GRPID | ATTR_CMN_ACCESSMASK | ATTR_CMN_FLAGS)

#define MAKE_DELETED_NAME(NAME,FID) \
	    (void) sprintf((NAME), "%s%d", HFS_DELETE_PREFIX, (FID))


extern uid_t console_user;

/* Global vfs data structures for hfs */
int (**hfs_vnodeop_p)(void *);

/* external routines defined in hfs_vhash.c */
extern void hfs_vhashrem(struct hfsnode *hp);
extern int 	vinvalbuf_vhash(register struct vnode *vp, int flags, struct ucred *cred, struct proc *p);
extern void hfs_vhashmove( struct hfsnode *hp,UInt32 nodeID);
extern struct vnode * hfs_vhashget(dev_t dev, UInt32 nodeID, UInt8 forkType);

extern OSErr PositionIterator(CatalogIterator *cip, UInt32 offset, BTreeIterator *bip, UInt16 *op);

extern void hfs_name_CatToMeta(CatalogNodeData *nodeData, struct hfsfilemeta *fm);

extern groupmember(gid_t gid, struct ucred *cred);

static int hfs_makenode( int mode,
	dev_t rawdev, struct vnode *dvp, struct vnode **vpp,
	struct componentname *cnp, struct proc *p);

static void hfs_chid(struct hfsnode *hp, u_int32_t fid, u_int32_t pid, char* name);

static int hfs_write_access(struct vnode *vp, struct ucred *cred, struct proc *p, Boolean considerFlags);

static int hfs_chown( struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred, struct proc *p);
static int hfs_chmod( struct vnode *vp, int mode, struct ucred *cred, struct proc *p);
static int hfs_chflags( struct vnode *vp, u_long flags, struct ucred *cred, struct proc *p);


int hfs_cache_lookup();		/* in hfs_lookup.c */
int hfs_lookup();		/* in hfs_lookup.c */
int hfs_read();			/* in hfs_readwrite.c */
int hfs_write();		/* in hfs_readwrite.c */
int hfs_ioctl();		/* in hfs_readwrite.c */
int hfs_select();		/* in hfs_readwrite.c */
int hfs_mmap();			/* in hfs_readwrite.c */
int hfs_seek();			/* in hfs_readwrite.c */
int hfs_bmap();			/* in hfs_readwrite.c */
int hfs_strategy();		/* in hfs_readwrite.c */
int hfs_reallocblks();	/* in hfs_readwrite.c */
int hfs_truncate();		/* in hfs_readwrite.c */
int hfs_allocate();		/* in hfs_readwrite.c */
int hfs_pagein();		/* in hfs_readwrite.c */
int hfs_pageout();		/* in hfs_readwrite.c */
int hfs_search();		/* in hfs_search.c */
int hfs_bwrite();		/* in hfs_readwrite.c */
int hfs_link();			/* in hfs_link.c */
int hfs_blktooff();		/* in hfs_readwrite.c */
int hfs_offtoblk();		/* in hfs_readwrite.c */
int hfs_cmap();		/* in hfs_readwrite.c */

/*****************************************************************************
*
*	Operations on vnodes
*
*****************************************************************************/

/*
 * Create a regular file
#% create	dvp	L U U
#% create	vpp	- L -
#
 vop_create {
     IN WILLRELE struct vnode *dvp;
     OUT struct vnode **vpp;
     IN struct componentname *cnp;
     IN struct vattr *vap;
	
     We are responsible for freeing the namei buffer,
	 it is done in hfs_makenode()
*/

static int
hfs_create(ap)
struct vop_create_args /* {
    struct vnode *a_dvp;
    struct vnode **a_vpp;
    struct componentname *a_cnp;
    struct vattr *a_vap;
} */ *ap;
{
	struct proc		*p = current_proc();
    int				retval;
    int				mode = MAKEIMODE(ap->a_vap->va_type, ap->a_vap->va_mode);
    DBG_FUNC_NAME("create");
    DBG_VOP_LOCKS_DECL(2);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_PRINT_VNODE_INFO(ap->a_dvp);
    DBG_VOP_PRINT_CPN_INFO(ap->a_cnp);

    DBG_VOP_LOCKS_INIT(0,ap->a_dvp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
    DBG_VOP_LOCKS_INIT(1,*ap->a_vpp, VOPDBG_IGNORE, VOPDBG_LOCKED, VOPDBG_IGNORE, VOPDBG_POS);
    DBG_VOP_CONT(("\tva_type %d va_mode 0x%x\n",
             ap->a_vap->va_type, ap->a_vap->va_mode));

#if HFS_DIAGNOSTIC
    DBG_HFS_NODE_CHECK(ap->a_dvp);
    DBG_ASSERT(ap->a_dvp->v_type == VDIR);
    if(ap->a_vap == NULL) {
        panic("NULL attr on create");
    }

    switch(ap->a_vap->va_type) {
        case VDIR:
    		VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
            vput(ap->a_dvp);
            DBG_VOP_LOCKS_TEST(EISDIR);
            return (EISDIR);	/* use hfs_mkdir instead */
        case VREG:
        case VLNK:
            break;
        default:
            DBG_ERR(("%s: INVALID va_type: %d, %s, %s\n", funcname, ap->a_vap->va_type, H_NAME(VTOH(ap->a_dvp)), ap->a_cnp->cn_nameptr));
    		VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
            vput(ap->a_dvp);
            DBG_VOP_LOCKS_TEST(EINVAL);
            return (EINVAL);
            }
//    if(ap->a_vap->va_mode & (VSUID | VSGID | VSVTX)) {
//        DBG_ERR(("%s: INVALID va_mode (%o): %s, %s\n", funcname, ap->a_vap->va_mode, H_NAME(VTOH(ap->a_dvp)), ap->a_cnp->cn_nameptr));
//        DBG_VOP_LOCKS_TEST(EINVAL);
//		  VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
//        vput(ap->a_dvp);
//        return (EINVAL);		/* Can't do these */
//    };
#endif

	/* Create the vnode */
    retval = hfs_makenode(mode, 0, ap->a_dvp, ap->a_vpp, ap->a_cnp, p);
    DBG_VOP_UPDATE_VP(1, *ap->a_vpp);

    if (retval != E_NONE) {
        DBG_ERR(("%s: hfs_makenode FAILED: %s, %s\n", funcname, ap->a_cnp->cn_nameptr, H_NAME(VTOH(ap->a_dvp))));
	}
    DBG_VOP_LOCKS_TEST(retval);
    return (retval);
}


/*
 * Mknod vnode call

#% mknod	dvp	L U U
#% mknod	vpp	- X -
#
 vop_mknod {
     IN WILLRELE struct vnode *dvp;
     OUT WILLRELE struct vnode **vpp;
     IN struct componentname *cnp;
     IN struct vattr *vap;
     */
/* ARGSUSED */

static int
hfs_mknod(ap)
struct vop_mknod_args /* {
    struct vnode *a_dvp;
    struct vnode **a_vpp;
    struct componentname *a_cnp;
    struct vattr *a_vap;
} */ *ap;
{
	struct vattr *vap = ap->a_vap;
	struct vnode **vpp = ap->a_vpp;
    struct proc *p = current_proc();
	dev_t rawdev = 0;
	int error;

	if (VTOVCB(ap->a_dvp)->vcbSigWord != kHFSPlusSigWord) {
		VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
		vput(ap->a_dvp);
		return (EOPNOTSUPP);
	}

	if (vap->va_rdev != VNOVAL) {
		/*
		 * Want to be able to use this to make badblock
		 * inodes, so don't truncate the dev number.
		 */
		rawdev = vap->va_rdev;
	}

	/* Create the vnode */
	error = hfs_makenode(MAKEIMODE(vap->va_type, vap->va_mode),
				rawdev, ap->a_dvp, vpp, ap->a_cnp, p);

	if (error != E_NONE) {
		return (error);
	}

	/*
	 * Remove inode so that it will be reloaded by lookup and
	 * checked to see if it is an alias of an existing vnode.
	 * Note: unlike UFS, we don't bash v_type here.
	 */
	vput(*vpp);
	vgone(*vpp);
	*vpp = 0;
	return (0);
}


/*
 * mkcomplex vnode call
 *

#% mkcomplex	dvp	L U U
#% mkcomplex	vpp	- L -
#
vop_mkcomplex {
	IN WILLRELE struct vnode *dvp;
	OUT struct vnode **vpp;
	IN struct componentname *cnp;
	IN struct vattr *vap;
	IN u_long type;
}

 */
 
static int
hfs_mkcomplex(ap)
struct vop_mkcomplex_args /* {
	struct vnode *a_dvp;
	struct vnode **a_vpp;
	struct componentname *a_cnp;
	struct vattr *a_vap;
	u_long a_type;
} */ *ap;
{
    int		retval = E_NONE;
    DBG_FUNC_NAME("make_complex");
    DBG_VOP_LOCKS_DECL(2);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_PRINT_VNODE_INFO(ap->a_dvp);
    DBG_VOP_PRINT_CPN_INFO(ap->a_cnp);DBG_VOP_CONT(("\n"));

    DBG_VOP_LOCKS_INIT(0,ap->a_dvp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
    DBG_VOP_LOCKS_INIT(1,*ap->a_vpp, VOPDBG_IGNORE, VOPDBG_LOCKED, VOPDBG_IGNORE, VOPDBG_POS);

    retval = VOP_CREATE(ap->a_dvp, ap->a_vpp, ap->a_cnp, ap->a_vap);

    DBG_VOP_LOCKS_TEST(retval);
    return retval;
}


/*
 * Open called.
#% open		vp	L L L
#
 vop_open {
     IN struct vnode *vp;
     IN int mode;
     IN struct ucred *cred;
     IN struct proc *p;
     */


static int
hfs_open(ap)
struct vop_open_args /* {
    struct vnode *a_vp;
    int  a_mode;
    struct ucred *a_cred;
    struct proc *a_p;
} */ *ap;
{
	struct hfsnode	*hp = VTOH(ap->a_vp);
	int				retval = E_NONE;
	DBG_FUNC_NAME("open");
	DBG_VOP_LOCKS_DECL(1);
	DBG_VOP_PRINT_FUNCNAME();
	DBG_VOP_CONT((" "));DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));
	DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_POS);

    if (ap->a_vp->v_type == VREG)	 /* Only files */
      {	
        /*
         * Files marked append-only must be opened for appending.
         */
        if ((hp->h_meta->h_pflags & APPEND) &&
            (ap->a_mode & (FWRITE | O_APPEND)) == FWRITE)
            retval = EPERM;
        }


    DBG_VOP_LOCKS_TEST(retval);
    return (retval);
}

/*
 * Close called.
 *
 * Update the times on the hfsnode.
#% close	vp	U U U
#
 vop_close {
     IN struct vnode *vp;
     IN int fflag;
     IN struct ucred *cred;
     IN struct proc *p;
     */


static int
hfs_close(ap)
struct vop_close_args /* {
    struct vnode *a_vp;
    int  a_fflag;
    struct ucred *a_cred;
    struct proc *a_p;
} */ *ap;
{
    register struct vnode	*vp = ap->a_vp;
    struct hfsnode 			*hp = VTOH(vp);
    struct proc				*p = ap->a_p;
	FCB						*fcb;
    struct timeval 			tv;
	off_t					leof;
	u_long					blks, blocksize;
    int 					retval = E_NONE;

	DBG_FUNC_NAME("close");
	DBG_VOP_LOCKS_DECL(1);
	DBG_VOP_PRINT_FUNCNAME();
	DBG_VOP_CONT((" "));DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));
	DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);

	simple_lock(&vp->v_interlock);
	if (vp->v_usecount > (UBCINFOEXISTS(vp) ? 2 : 1)) {
		tv = time;
		HFSTIMES(hp, &tv, &tv);
	}
	simple_unlock(&vp->v_interlock);

	/*
	 * VOP_CLOSE can be called with vp locked (from vclean).
	 * We check for this case using VOP_ISLOCKED and bail.
	 *
	 * also, ignore complex nodes; there's no data associated with them.
	 */
	if (H_FORKTYPE(hp) == kDirectory || VOP_ISLOCKED(vp)) {
		DBG_VOP_LOCKS_TEST(E_NONE);
		return E_NONE;
	};

	fcb = HTOFCB(hp);
	leof = fcb->fcbEOF;
	
	if (leof != 0) {
		enum vtype our_type = vp->v_type;
		u_long our_id = vp->v_id;
		
		vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, p);
		/*
		 * Since we can contact switch in vn_lock our vnode
		 * could get recycled (eg umount -f).  Double check
		 * that its still ours.
		 */
		if (vp->v_type != our_type || vp->v_id != our_id) {
			VOP_UNLOCK(vp, 0, p);
			DBG_VOP_LOCKS_TEST(E_NONE);
			return(E_NONE);
		}

		blocksize = HTOVCB(hp)->blockSize;
		blks = leof / blocksize;
		if (((off_t)blks * (off_t)blocksize) != leof)
			blks++;
	
		/*
		 * Shrink the peof to the smallest size neccessary to contain the leof.
		 */
		if (((off_t)blks * (off_t)blocksize) < fcb->fcbPLen) {
	 		retval = VOP_TRUNCATE(vp, leof, IO_NDELAY, ap->a_cred, p);
		}
		cluster_push(vp);
		VOP_UNLOCK(vp, 0, p);
	}

	DBG_VOP_LOCKS_TEST(retval);
	return (retval);
}

/*
#% access	vp	L L L
#
 vop_access {
     IN struct vnode *vp;
     IN int mode;
     IN struct ucred *cred;
     IN struct proc *p;

     */

static int
hfs_access(ap)
struct vop_access_args /* {
    struct vnode *a_vp;
    int  a_mode;
    struct ucred *a_cred;
    struct proc *a_p;
} */ *ap;
{
    struct vnode *vp 			= ap->a_vp;
    struct ucred *cred 			= ap->a_cred;
    struct hfsnode *hp 			= VTOH(vp);
    ExtendedVCB	*vcb			= HTOVCB(hp);
    register gid_t *gp;
    mode_t mask, mode;
    Boolean isHFSPlus;
    int retval 					= E_NONE;
    int i;
    DBG_FUNC_NAME("access");
    DBG_VOP_LOCKS_DECL(1);
//    DBG_VOP_PRINT_FUNCNAME();
//    DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));

    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_POS);
   
	 mode 		= ap->a_mode;
     isHFSPlus	= (vcb->vcbSigWord == kHFSPlusSigWord );

     /*
      * Disallow write attempts on read-only file systems;
      * unless the file is a socket, fifo, or a block or
      * character device resident on the file system.
      */
     if (mode & VWRITE) {
         switch (vp->v_type) {
         case VDIR:
         case VLNK:
         case VREG:
             if (VTOVFS(vp)->mnt_flag & MNT_RDONLY)
                 return (EROFS);
             break;
		default:
			break;
         }
     }

     /* If immutable bit set, nobody gets to write it. */
     if ((mode & VWRITE) && (hp->h_meta->h_pflags & IMMUTABLE))
         return (EPERM);

     /* Otherwise, user id 0 always gets access. */
     if (ap->a_cred->cr_uid == 0) {
         retval = 0;
         goto Exit;
     };

     mask = 0;

    /* Otherwise, check the owner. */
    if (hfs_owner_rights(vp, cred, ap->a_p, false) == 0) {
        if (mode & VEXEC)
            mask |= S_IXUSR;
        if (mode & VREAD)
            mask |= S_IRUSR;
        if (mode & VWRITE)
            mask |= S_IWUSR;
        retval =  ((hp->h_meta->h_mode & mask) == mask ? 0 : EACCES);
        goto Exit;
    }

    /* Otherwise, check the groups. */
    if (! (VTOVFS(vp)->mnt_flag & MNT_UNKNOWNPERMISSIONS)) {
	    for (i = 0, gp = cred->cr_groups; i < cred->cr_ngroups; i++, gp++) {
	        if (hp->h_meta->h_gid == *gp) {
	            if (mode & VEXEC)
	                mask |= S_IXGRP;
	            if (mode & VREAD)
	                mask |= S_IRGRP;
	            if (mode & VWRITE)
	                mask |= S_IWGRP;
	            retval = ((hp->h_meta->h_mode & mask) == mask ? 0 : EACCES);
				goto Exit;
	        }
	    };
	};

    /* Otherwise, check everyone else. */
    if (mode & VEXEC)
        mask |= S_IXOTH;
    if (mode & VREAD)
        mask |= S_IROTH;
    if (mode & VWRITE)
        mask |= S_IWOTH;
    retval = ((hp->h_meta->h_mode & mask) == mask ? 0 : EACCES);

Exit:
	DBG_VOP_LOCKS_TEST(retval);
	return (retval);    
}



/*
#% getattr	vp	= = =
#
 vop_getattr {
     IN struct vnode *vp;
     IN struct vattr *vap;
     IN struct ucred *cred;
     IN struct proc *p;

     */


/* ARGSUSED */
static int
hfs_getattr(ap)
struct vop_getattr_args /* {
    struct vnode *a_vp;
    struct vattr *a_vap;
    struct ucred *a_cred;
    struct proc *a_p;
} */ *ap;
{
    register struct vnode 	*vp = ap->a_vp;
    register struct hfsnode *hp = VTOH(vp);
    register struct vattr	*vap = ap->a_vap;
    struct timeval 			tv;
    DBG_FUNC_NAME("getattr");
    DBG_VOP_LOCKS_DECL(1);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));

    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_SAME, VOPDBG_SAME, VOPDBG_SAME, VOPDBG_POS);

    DBG_HFS_NODE_CHECK(ap->a_vp);

    tv = time;
    HFSTIMES(hp, &tv, &tv);

    vap->va_fsid = H_DEV(hp);
    vap->va_fileid = H_FILEID(hp);
    vap->va_mode = hp->h_meta->h_mode;
    if (VTOVFS(vp)->mnt_flag & MNT_UNKNOWNPERMISSIONS) {
    	vap->va_uid = (VTOHFS(vp)->hfs_uid == UNKNOWNUID) ? console_user : VTOHFS(vp)->hfs_uid;
    } else {
    	vap->va_uid = (hp->h_meta->h_uid == UNKNOWNUID) ? console_user : hp->h_meta->h_uid;
    };
    vap->va_gid = hp->h_meta->h_gid;
    if (vp->v_type == VDIR) {
        vap->va_size = hp->h_meta->h_size;
        vap->va_bytes = 0;
        vap->va_rdev = 0;
	vap->va_nlink = hp->h_meta->h_nlink;
		/* 
		 * account for hidden data nodes directory
		 */
		if ((H_FILEID(hp) == kRootDirID) &&
		    (VTOHFS(vp)->hfs_private_metadata_dir != 0)) {
			vap->va_size -= AVERAGE_HFSDIRENTRY_SIZE;
			vap->va_nlink--;
		}
    }
    else {
        vap->va_size = hp->fcbEOF;
        vap->va_bytes = hp->h_meta->h_size;

		if (vp->v_type == VBLK || vp->v_type == VCHR)
			vap->va_rdev = hp->h_meta->h_rdev;
		else
			vap->va_rdev = 0;

		if (hp->h_meta->h_metaflags & IN_DELETED)
			vap->va_nlink = 0;
#if HFS_HARDLINKS
		else if ((hp->h_meta->h_metaflags & IN_DATANODE) &&
			 (hp->h_meta->h_nlink > 0))
			vap->va_nlink = hp->h_meta->h_nlink;
#endif
		else
			vap->va_nlink = 1;

    }

    vap->va_atime.tv_nsec = 0;
    vap->va_atime.tv_sec = hp->h_meta->h_atime;
    vap->va_mtime.tv_nsec = 0;
    vap->va_mtime.tv_sec = hp->h_meta->h_mtime;
    vap->va_ctime.tv_nsec = 0;
    vap->va_ctime.tv_sec = hp->h_meta->h_ctime;
    vap->va_flags = hp->h_meta->h_pflags;
    vap->va_gen = 0;
    /* this doesn't belong here */
    if (vp->v_type == VBLK)
        vap->va_blocksize = BLKDEV_IOSIZE;
    else if (vp->v_type == VCHR)
        vap->va_blocksize = MAXPHYSIO;
    else
        vap->va_blocksize = VTOVFS(vp)->mnt_stat.f_iosize;
	vap->va_type = vp->v_type;
    vap->va_filerev = 0;

    DBG_VOP_LOCKS_TEST(E_NONE);
    return (E_NONE);
}

/*
 * Set attribute vnode op. called from several syscalls
#% setattr	vp	L L L
#
 vop_setattr {
     IN struct vnode *vp;
     IN struct vattr *vap;
     IN struct ucred *cred;
     IN struct proc *p;

     */

static int
hfs_setattr(ap)
struct vop_setattr_args /* {
struct vnode *a_vp;
struct vattr *a_vap;
struct ucred *a_cred;
struct proc *a_p;
} */ *ap;
{
    struct vnode 	*vp = ap->a_vp;
    struct hfsnode 	*hp = VTOH(vp);
    struct vattr 	*vap = ap->a_vap;
    struct ucred 	*cred = ap->a_cred;
    struct proc 	*p = ap->a_p;
    struct timeval 	atimeval, mtimeval;
    int				retval;
    DBG_FUNC_NAME("setattr");
    DBG_VOP_LOCKS_DECL(1);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));
    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_POS);
    WRITE_CK(vp, funcname);
    DBG_HFS_NODE_CHECK(ap->a_vp);

    /*
     * Check for unsettable attributes.
     */
    if ((vap->va_type != VNON) || (vap->va_nlink != VNOVAL) ||
        (vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) ||
        (vap->va_blocksize != VNOVAL) || (vap->va_rdev != VNOVAL) ||
        ((int)vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL)) {
        retval = EINVAL;
        goto ErrorExit;
    }

    if (vap->va_flags != VNOVAL) {
        if (VTOVFS(vp)->mnt_flag & MNT_RDONLY) {
            retval = EROFS;
            goto ErrorExit;
        };
        if ((retval = hfs_chflags(vp, vap->va_flags, cred, p))) {
            goto ErrorExit;
        };
        if (vap->va_flags & (IMMUTABLE | APPEND)) {
            retval = 0;
            goto ErrorExit;
        };
    }

    if (hp->h_meta->h_pflags & (IMMUTABLE | APPEND)) {
        retval = EPERM;
        goto ErrorExit;
    };
    /*
     * Go through the fields and update iff not VNOVAL.
     */
    if (vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL) {
        if (VTOVFS(vp)->mnt_flag & MNT_RDONLY) {
            retval = EROFS;
            goto ErrorExit;
        };
        if ((retval = hfs_chown(vp, vap->va_uid, vap->va_gid, cred, p))) {
            goto ErrorExit;
        };
    }
    if (vap->va_size != VNOVAL) {
        /*
         * Disallow write attempts on read-only file systems;
         * unless the file is a socket, fifo, or a block or
         * character device resident on the file system.
         */
        switch (vp->v_type) {
            case VDIR:
                retval = EISDIR;
                goto ErrorExit;
            case VLNK:
            case VREG:
                if (VTOVFS(vp)->mnt_flag & MNT_RDONLY) {
                    retval = EROFS;
                    goto ErrorExit;
                };
                break;
            default:
                break;
        }
        if ((retval = VOP_TRUNCATE(vp, vap->va_size, 0, cred, p))) {
            goto ErrorExit;
        };
    }
    hp = VTOH(vp);
    if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) {
        if (VTOVFS(vp)->mnt_flag & MNT_RDONLY) {
            retval = EROFS;
            goto ErrorExit;
        };
        if (((retval = hfs_owner_rights(vp, cred, p, true)) != 0) &&
            ((vap->va_vaflags & VA_UTIMES_NULL) == 0 ||
             (retval = VOP_ACCESS(vp, VWRITE, cred, p)))) {
            goto ErrorExit;
        };
        if (vap->va_atime.tv_sec != VNOVAL)
            hp->h_nodeflags |= IN_ACCESS;
        if (vap->va_mtime.tv_sec != VNOVAL) {
            hp->h_nodeflags |= IN_CHANGE | IN_UPDATE;
	    /*
	     * The utimes system call can reset the modification time
	     * but it doesn't know about the HFS+ create time.  So we
	     * need to insure that the creation time is always at least
	     * as old as the modification time.
	     */
	    if (( VTOVCB(vp)->vcbSigWord == kHFSPlusSigWord )  &&
	        ( H_FILEID(hp) != kRootDirID )  &&
	        ( vap->va_mtime.tv_sec < hp->h_meta->h_crtime ))
		hp->h_meta->h_crtime = vap->va_mtime.tv_sec;
	}
        atimeval.tv_sec = vap->va_atime.tv_sec;
        atimeval.tv_usec = 0;
        mtimeval.tv_sec = vap->va_mtime.tv_sec;
        mtimeval.tv_usec = 0;
        if ((retval = VOP_UPDATE(vp, &atimeval, &mtimeval, 1))) {
            goto ErrorExit;
        };
    }
    retval = 0;
    if (vap->va_mode != (mode_t)VNOVAL) {
        if (VTOVFS(vp)->mnt_flag & MNT_RDONLY) {
            retval = EROFS;
            goto ErrorExit;
        };
        retval = hfs_chmod(vp, (int)vap->va_mode, cred, p);
    };

ErrorExit: ;

    DBG_VOP(("hfs_setattr: returning %d...\n", retval));
    DBG_VOP_LOCKS_TEST(retval);
    return (retval);
}


/*

#
#% getattrlist	vp	= = =
#
 vop_getattrlist {
     IN struct vnode *vp;
     IN struct attrlist *alist;
     INOUT struct uio *uio;
     IN struct ucred *cred;
     IN struct proc *p;
 };

 */

static int
hfs_getattrlist(ap)
struct vop_getattrlist_args /* {
struct vnode *a_vp;
struct attrlist *a_alist
struct uio *a_uio;
struct ucred *a_cred;
struct proc *a_p;
} */ *ap;
{
    struct vnode *vp = ap->a_vp;
    struct hfsnode *hp = VTOH(vp);
    struct attrlist *alist = ap->a_alist;
    int error = 0;
    struct hfsCatalogInfo catInfo;
    struct hfsCatalogInfo *catInfoPtr = NULL;
    struct timeval 			tv;
    int fixedblocksize;
    int attrblocksize;
    int attrbufsize;
    void *attrbufptr;
    void *attrptr;
    void *varptr;
    u_int32_t fileID;
    DBG_FUNC_NAME("getattrlist");
    DBG_VOP_LOCKS_DECL(1);

    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_SAME, VOPDBG_SAME, VOPDBG_SAME, VOPDBG_POS);
    DBG_HFS_NODE_CHECK(ap->a_vp);
    DBG_VOP(("%s: Common attr:0x%lx, buff size Ox%lX,\n",funcname, (u_long)alist->commonattr,(u_long)ap->a_uio->uio_resid));

    DBG_ASSERT(ap->a_uio->uio_rw == UIO_READ);

    if ((alist->bitmapcount != ATTR_BIT_MAP_COUNT) ||
        ((alist->commonattr & ~ATTR_CMN_VALIDMASK) != 0) ||
        ((alist->volattr & ~ATTR_VOL_VALIDMASK) != 0) ||
        ((alist->dirattr & ~ATTR_DIR_VALIDMASK) != 0) ||
        ((alist->fileattr & ~ATTR_FILE_VALIDMASK) != 0) ||
        ((alist->forkattr & ~ATTR_FORK_VALIDMASK) != 0)) {
        DBG_ERR(("%s: bad attrlist\n", funcname));
        DBG_VOP_LOCKS_TEST(EINVAL);
        return EINVAL;
        };

    /* Requesting volume information requires setting the ATTR_VOL_INFO bit and
        volume info requests are mutually exclusive with all other info requests: */
   if ((alist->volattr != 0) && (((alist->volattr & ATTR_VOL_INFO) == 0) ||
        (alist->dirattr != 0) || (alist->fileattr != 0) || (alist->forkattr != 0)
		)) {
        DBG_ERR(("%s: conflicting information requested\n", funcname));
        DBG_VOP_LOCKS_TEST(EINVAL);
        return EINVAL;
        };

    /* Reject requests for unsupported options for now: */
    if ((alist->commonattr & (ATTR_CMN_NAMEDATTRCOUNT | ATTR_CMN_NAMEDATTRLIST)) ||
        (alist->fileattr & (ATTR_FILE_FILETYPE | ATTR_FILE_FORKCOUNT | ATTR_FILE_FORKLIST))) {
        DBG_ERR(("%s: illegal bits in attlist\n", funcname));
        DBG_VOP_LOCKS_TEST(EINVAL);
        return EINVAL;
        };

	/* Requesting volume information requires root vnode */ 
    if ((alist->volattr) && (H_FILEID(hp) != kRootDirID)) {
        DBG_ERR(("%s: not root vnode\n", funcname));
        DBG_VOP_LOCKS_TEST(EINVAL);
        return EINVAL;
    	};


	/* Update times if needed */
	tv = time;
    HFSTIMES(hp, &tv, &tv);

	/* If a FileID (ATTR_CMN_OBJPERMANENTID) is requested on an HFS volume we must be sure
		to create the thread record before returning it:
		*/
	if ((vp->v_type == VREG) &&
		(alist->commonattr & ATTR_CMN_OBJPERMANENTID)) {
		/* Only HFS-Plus volumes are guaranteed to have a thread record in place already: */
		if (VTOVCB(vp)->vcbSigWord != kHFSPlusSigWord) {
			/* Create a thread record and return the FileID [which is the file's fileNumber] */
			/* lock catalog b-tree */
			error = hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_EXCLUSIVE, ap->a_p);
			error = hfsCreateFileID(VTOVCB(vp), H_DIRID(hp), H_NAME(hp), H_HINT(hp), &fileID);
			(void) hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_RELEASE, ap->a_p);
			if (error) {
				DBG_VOP_LOCKS_TEST(error);
				DBG_ERR(("hfs_getattrlist: error %d on CreateFileIDRef.\n", error));
				return error;
			};
			DBG_ASSERT(fileID == H_FILEID(hp));
		};
	};

	/* Asking for data fork attributes from the rsrc fork is not supported */
	if ((H_FORKTYPE(hp) == kRsrcFork) && (alist->fileattr & HFS_ATTR_FILE_LOOKUPMASK)) {
		return (EINVAL);
	}
	
    /*
	 * Avoid unnecessary catalog lookups for volume info which is available directly
	 * in the VCB and root vnode, or can be synthesized.
	 */
	INIT_CATALOGDATA(&catInfo.nodeData, 0);
	catInfo.hint = kNoHint;

    if (((alist->volattr == 0) && ((alist->commonattr & HFS_ATTR_CMN_LOOKUPMASK) != 0)) ||
        ((alist->dirattr & HFS_ATTR_DIR_LOOKUPMASK) != 0) ||
        ((alist->fileattr & HFS_ATTR_FILE_LOOKUPMASK) != 0)) {

        /* lock catalog b-tree */
        error = hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_SHARED, ap->a_p);
        if (error) goto GetCatalogErr_Exit;

        if (alist->volattr != 0) {
            /* Look up the root info, regardless of the vnode provided */
            error = hfs_getcatalog(VTOVCB(vp), 2, NULL,  -1, &catInfo);
        } else {
            error = hfs_getcatalog(VTOVCB(vp), H_DIRID(hp), H_NAME(hp),  -1, &catInfo);
            if (error == 0) H_HINT(hp) = catInfo.hint;						/* Remember the last valid hint */
        };
        
        /* unlock catalog b-tree */
        (void) hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_RELEASE, ap->a_p);

        /*
		 * If a data fork has an active sibling and we need
		 * rsrc fork attributes then we need to lock the
		 * sibling and make a copy of its attributes.
		 */
        if ((hp->h_meta->h_usecount > 1)   &&
			(H_FORKTYPE(hp) == kDataFork)  &&
			(alist->fileattr & HFS_ATTR_FILE_LOOKUPMASK)) {
            struct vnode *sib_vp = NULL;
            struct hfsnode *nhp;
            struct proc *p = current_proc();
            
            DBG_ASSERT(hp->h_meta->h_siblinghead.cqh_first && 
                            (hp->h_meta->h_siblinghead.cqh_first != hp->h_meta->h_siblinghead.cqh_last));
            DBG_ASSERT(H_FORKTYPE(hp)==kDataFork || H_FORKTYPE(hp)==kRsrcFork);
    
            /* Loop through all siblings, skipping ourselves */
            simple_lock(&hp->h_meta->h_siblinglock);
            CIRCLEQ_FOREACH(nhp, &hp->h_meta->h_siblinghead, h_sibling) {
                if (nhp == hp)		/* skip ourselves */
                    continue;
                sib_vp = HTOV(nhp);
            };
            simple_unlock(&hp->h_meta->h_siblinglock);
    
            /* The only error that vget returns is when the vnode is going away, so ignore the vnode */
            if (vget(sib_vp, LK_EXCLUSIVE | LK_RETRY, p) == 0) {
                if (VTOH(sib_vp)->h_nodeflags & (IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE)) {
                    /* XXX SER No need to copy the whole thing over, just copy the fork info */
                    CopyVNodeToCatalogNode (sib_vp, &catInfo.nodeData);
                };
    
                vput(sib_vp);
            };	/* vget() */
        };	/* h_use_count > 1 */

        /* Update to the in-memory state, if it has been modified...just to make sure */
        if (VTOH(vp)->h_nodeflags & (IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE)) {
            /* XXX SER No need to copy the whole thing over, just copy the fork info */
            CopyVNodeToCatalogNode (vp, &catInfo.nodeData);
        };
 
         /* XXX What if hfs_getcatalog fails...we just continue??? */
        catInfoPtr = &catInfo;

   };

    fixedblocksize = AttributeBlockSize(alist);
    attrblocksize = fixedblocksize + (sizeof(u_long));							/* u_long for length longword */
    if (alist->commonattr & ATTR_CMN_NAME) attrblocksize += kHFSPlusMaxFileNameBytes + 1;
    if (alist->commonattr & ATTR_CMN_NAMEDATTRLIST) attrblocksize += 0;			/* XXX PPD */
    if (alist->volattr & ATTR_VOL_MOUNTPOINT) attrblocksize += PATH_MAX;
    if (alist->volattr & ATTR_VOL_NAME) attrblocksize += kHFSPlusMaxFileNameBytes + 1;
    if (alist->fileattr & ATTR_FILE_FORKLIST) attrblocksize += 0;				/* XXX PPD */

    attrbufsize = MIN(ap->a_uio->uio_resid, attrblocksize);
    DBG_VOP(("hfs_getattrlist: allocating Ox%X byte buffer (Ox%X + Ox%X) for attributes...\n",
             attrblocksize,
             fixedblocksize,
             attrblocksize - fixedblocksize));
    MALLOC(attrbufptr, void *, attrblocksize, M_TEMP, M_WAITOK);
    attrptr = attrbufptr;
    *((u_long *)attrptr) = 0;									/* Set buffer length in case of errors */
    ++((u_long *)attrptr);										/* Reserve space for length field */
    varptr = ((char *)attrptr) + fixedblocksize;				/* Point to variable-length storage */
    DBG_VOP(("hfs_getattrlist: attrptr = 0x%08X, varptr = 0x%08X...\n", (u_int)attrptr, (u_int)varptr));

    PackAttributeBlock(alist, vp, catInfoPtr, &attrptr, &varptr);
    attrbufsize = MIN(attrbufsize, (u_int)varptr - (u_int)attrbufptr);	/* Don't copy out more data than was generated */
    *((u_long *)attrbufptr) = attrbufsize;						/* Set actual buffer length for return to caller */
    DBG_VOP(("hfs_getattrlist: copying Ox%X bytes to user address 0x%08X.\n", attrbufsize, (u_int)ap->a_uio->uio_iov->iov_base));
    error = uiomove((caddr_t)attrbufptr, attrbufsize, ap->a_uio);
    if (error != E_NONE) {
        DBG_ERR(("hfs_getattrlist: error %d on uiomove.\n", error));
        };

    FREE(attrbufptr, M_TEMP);


GetCatalogErr_Exit:
	CLEAN_CATALOGDATA(&catInfo.nodeData);
    DBG_VOP_LOCKS_TEST(error);
    return error;
}



/*

#
#% setattrlist	vp	L L L
#
 vop_setattrlist {
     IN struct vnode *vp;
     IN struct attrlist *alist;
     INOUT struct uio *uio;
     IN struct ucred *cred;
     IN struct proc *p;
 };

 */

static int
hfs_setattrlist(ap)
struct vop_setattrlist_args /* {
struct vnode *a_vp;
struct attrlist *a_alist
struct uio *a_uio;
struct ucred *a_cred;
struct proc *a_p;
} */ *ap;
{
    struct vnode *vp = ap->a_vp;
    struct hfsnode *hp = VTOH(vp);
    struct attrlist *alist = ap->a_alist;
    struct ucred *cred = ap->a_cred;
    struct proc *p = ap->a_p;
    int error;
    struct hfsCatalogInfo catInfo;
    int attrblocksize;
    void *attrbufptr = NULL;
    void *attrptr;
    void *varptr = NULL;
	uid_t saved_uid;
	gid_t saved_gid;
	mode_t saved_mode;
    u_long saved_flags;
	char * filename;
	char iNodeName[32];
	u_int32_t pid;
    int retval = 0;

    DBG_FUNC_NAME("setattrlist");
    DBG_VOP_LOCKS_DECL(1);

    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_SAME, VOPDBG_SAME, VOPDBG_SAME, VOPDBG_POS);
    DBG_HFS_NODE_CHECK(ap->a_vp);
    DBG_VOP(("%s: Common attr:0x%x, buff size Ox%X,\n",funcname, (u_int)alist->commonattr,(u_int)ap->a_uio->uio_resid));

    DBG_ASSERT(ap->a_uio->uio_rw == UIO_WRITE);

    if ((alist->bitmapcount != ATTR_BIT_MAP_COUNT) ||
        ((alist->commonattr & ~ATTR_CMN_SETMASK) != 0) ||
        ((alist->volattr & ~ATTR_VOL_SETMASK) != 0) ||
        ((alist->dirattr & ~ATTR_DIR_SETMASK) != 0) ||
        ((alist->fileattr & ~ATTR_FILE_SETMASK) != 0) ||
        ((alist->forkattr & ~ATTR_FORK_SETMASK) != 0)) {
        DBG_ERR(("%s: Bad attrlist\n", funcname));
        DBG_VOP_LOCKS_TEST(EINVAL);
        return EINVAL;
        };

    if ((alist->volattr != 0) && 							/* Setting volume info */
		(((alist->volattr & ATTR_VOL_INFO) == 0) ||			/* Not explicitly indicating this or ... */
		 (alist->commonattr & ~ATTR_CMN_VOLSETMASK)))		/* ... setting invalid attributes for volume */
      {
        DBG_ERR(("%s: Bad attrlist\n", funcname));
        DBG_VOP_LOCKS_TEST(EINVAL);
        return EINVAL;
      };

    if (VTOVFS(vp)->mnt_flag & MNT_RDONLY) {
        DBG_VOP_LOCKS_TEST(EROFS);
        return EROFS;
    };

	/*
		Ownership of the file (in addition to write access, checked below,
		is required in one of two classes of calls:
		
		(a) When setting any ownership-requiring attribute other than ATTR_CMN_FLAGS, or
		(b) When setting ATTR_CMN_FLAGS on a volume that's not plain HFS (for which no
			real per-object ownership information is stored):
	 */
	if ((alist->commonattr & (OWNERSHIP_ONLY_ATTRS & ~ATTR_CMN_FLAGS)) ||
		((alist->commonattr & ATTR_CMN_FLAGS) && (VTOVCB(vp)->vcbSigWord != kHFSSigWord))) {
		/* NOTE: The following isn't ENTIRELY complete: even if you're the superuser
				 you cannot change the flags as long as SF_IMMUTABLE or SF_APPEND is
				 set and securelevel > 0.  This is verified in hfs_chflags which gets
				 invoked to do the actual flags field change so this check is sufficient
				 for now.
		 */
		/* Check to see if the user owns the object [or is superuser]: */
		if ((retval = hfs_owner_rights(vp, cred, p, true)) != 0) {
        	DBG_VOP_LOCKS_TEST(retval);
        	return retval;
        };
	} else {
		DBG_ASSERT(((alist->commonattr & OWNERSHIP_ONLY_ATTRS) == 0) ||
				   (((alist->commonattr & OWNERSHIP_ONLY_ATTRS) == ATTR_CMN_FLAGS) &&
					(VTOVCB(vp)->vcbSigWord == kHFSSigWord)));
		/* No ownership access is required: mere write access (checked below) will do... */
	};
	
	/* For any other attributes, check to see if the user has write access to
	    the object in question [unlike VOP_ACCESS, ignore IMMUTABLE here]: */
	    
	if ((((alist->commonattr & ~(OWNERSHIP_ONLY_ATTRS)) != 0) ||
		 (alist->volattr != 0) ||
		 (alist->dirattr != 0) ||
		 (alist->fileattr != 0) ||
		 (alist->forkattr != 0)) &&
		((retval = hfs_write_access(vp, cred, p, false)) != 0)) {
        DBG_VOP_LOCKS_TEST(retval);
        return retval;
	}; /* end of if ownership attr */
	
    /* Allocate the buffer now to minimize the time we might be blocked holding the catalog lock */
    attrblocksize = ap->a_uio->uio_resid;
    if (attrblocksize < AttributeBlockSize(alist)) {
        DBG_ERR(("%s: bad attrblocksize\n", funcname));
        DBG_VOP_LOCKS_TEST(EINVAL);
        return EINVAL;
    };

	MALLOC(attrbufptr, void *, attrblocksize, M_TEMP, M_WAITOK);

	INIT_CATALOGDATA(&catInfo.nodeData, kCatNameNoCopyName);
	catInfo.hint = kNoHint;
    
    /* lock catalog b-tree */
    error = hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_EXCLUSIVE, p);
    if (error != E_NONE) {
        goto FreeBuffer;
    };

	filename = H_NAME(hp);
	pid = H_DIRID(hp);

#if HFS_HARDLINKS
	/*
	 * Force an update of the indirect node instead of the link
	 * by using the name and parent of the indirect node.
	 */
	if (hp->h_meta->h_metaflags & IN_DATANODE) {
		MAKE_INODE_NAME(iNodeName, hp->h_meta->h_indnodeno);
		filename = iNodeName;
		pid = VTOHFS(vp)->hfs_private_metadata_dir;
	}
#endif


	error = hfs_getcatalog(VTOVCB(vp), pid, filename, -1, &catInfo);
    if (error != E_NONE) {
        DBG_ERR(("%s: Lookup failed on file '%s'\n", funcname,  filename));
        goto ErrorExit;
    };
    H_HINT(hp) = catInfo.hint;						/* Remember the last valid hint */

    error = uiomove((caddr_t)attrbufptr, attrblocksize, ap->a_uio);
    if (error) goto ErrorExit;

    if ((alist->volattr) && (H_FILEID(hp) != kRootDirID)) {
        error = EINVAL;
        goto ErrorExit;
    };

	/* do we have permission to change the dates? */
//  if (alist->commonattr & (ATTR_CMN_CRTIME | ATTR_CMN_MODTIME | ATTR_CMN_CHGTIME | ATTR_CMN_ACCTIME | ATTR_CMN_BKUPTIME)) {
    if (alist->commonattr & (ATTR_CMN_CHGTIME | ATTR_CMN_ACCTIME)) {
    	if ((error = hfs_owner_rights(vp, cred, p, true)) != 0) {
            goto ErrorExit;
        };
    };

    /* save these in case hfs_chown() or hfs_chmod() fail */
	saved_uid = hp->h_meta->h_uid;
	saved_gid = hp->h_meta->h_gid;
    saved_mode = hp->h_meta->h_mode;
    saved_flags = hp->h_meta->h_pflags;

    attrptr = attrbufptr;
    UnpackAttributeBlock(alist, vp, &catInfo, &attrptr, &varptr);

	/* if unpacking changed the owner or group then call hfs_chown() */
    if (saved_uid != hp->h_meta->h_uid || saved_gid != hp->h_meta->h_gid) {
		uid_t uid;
		gid_t gid;
		
		uid = hp->h_meta->h_uid;
 		hp->h_meta->h_uid = saved_uid;
		gid = hp->h_meta->h_gid;
		hp->h_meta->h_gid = saved_gid;
        if ((error = hfs_chown(vp, uid, gid, cred, p)))
			goto ErrorExit;
    }

	/* if unpacking changed the mode then call hfs_chmod() */
	if (saved_mode != hp->h_meta->h_mode) {
		mode_t mode;

		mode = hp->h_meta->h_mode;
		hp->h_meta->h_mode = saved_mode;
		if ((error = hfs_chmod(vp, mode, cred, p)))
			goto ErrorExit;
	};

    /* if unpacking changed the flags then call hfs_chflags */
    if (saved_flags != hp->h_meta->h_pflags) {
        u_long flags;

        flags = hp->h_meta->h_pflags;
        hp->h_meta->h_pflags = saved_flags;
        if ((error = hfs_chflags(vp, flags, cred, p)))
            goto ErrorExit;
    };

	if (alist->volattr == 0) {
		error = MacToVFSError( UpdateCatalogNode(HTOVCB(hp), pid, filename, H_HINT(hp), &catInfo.nodeData));
	}

   if (alist->volattr & ATTR_VOL_NAME) {
        ExtendedVCB *vcb 	= VTOVCB(vp);
        int			namelen = strlen(vcb->vcbVN);
    	
	if (vcb->vcbVN[0] == 0) {
	  /*
	    Ignore attempts to rename a volume to a zero-length name:
	    restore the original name from the metadata.
	   */
	  copystr(H_NAME(hp), vcb->vcbVN, sizeof(vcb->vcbVN), NULL);
	} else {
	  error = MoveRenameCatalogNode(vcb, kRootParID, H_NAME(hp), H_HINT(hp), kRootParID, vcb->vcbVN, &H_HINT(hp));
	  if (error) {
            VCB_LOCK(vcb);
            copystr(H_NAME(hp), vcb->vcbVN, sizeof(vcb->vcbVN), NULL);	/* Restore the old name in the VCB */
            vcb->vcbFlags |= 0xFF00;		// Mark the VCB dirty
            VCB_UNLOCK(vcb);
            goto ErrorExit;
	  };

		hfs_set_metaname(vcb->vcbVN, hp->h_meta, HTOHFS(hp));
		hp->h_nodeflags |= IN_CHANGE;
		
#if 0
	  /* if hfs wrapper exists, update its name too */
	  if (vcb->vcbSigWord == kHFSPlusSigWord && vcb->vcbAlBlSt != 0) {
	    HFSMasterDirectoryBlock *mdb;
	    struct buf *bp = NULL;
	    int size = kMDBSize;	/* 512 */
            int volnamelen = MIN(sizeof(Str27), namelen);
	    
	    if ( bread(VTOHFS(vp)->hfs_devvp, IOBLKNOFORBLK(kMasterDirectoryBlock, size),
		       IOBYTECCNTFORBLK(kMasterDirectoryBlock, kMDBSize, size), NOCRED, &bp) == 0) {
	      
	      mdb = (HFSMasterDirectoryBlock *)((char *)bp->b_data + IOBYTEOFFSETFORBLK(kMasterDirectoryBlock, size));
	      if (SWAP_BE16 (mdb->drSigWord) == kHFSSigWord) {
            /* Convert the string to MacRoman, ignoring any errors, */
            (void) utf8_to_hfs(vcb, volnamelen, vcb->vcbVN, Str31 mdb->drVN)
            bawrite(bp);
            bp = NULL;
	      }
	    }
	    
	    if (bp) brelse(bp);
	  }
#endif
	}; /* vcb->vcbVN[0] == 0 ... else ... */
   }; /* alist->volattr & ATTR_VOL_NAME */

ErrorExit:
    /* unlock catalog b-tree */
    (void) hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_RELEASE, p);

	CLEAN_CATALOGDATA(&catInfo.nodeData);

FreeBuffer:
    if (attrbufptr) FREE(attrbufptr, M_TEMP);

    DBG_VOP_LOCKS_TEST(error);
    return error;
}

/*
 * Change the mode on a file.
 * Inode must be locked before calling.
 */
static int
hfs_chmod(vp, mode, cred, p)
register struct vnode *vp;
register int mode;
register struct ucred *cred;
struct proc *p;
{
    register struct hfsnode *hp = VTOH(vp);
    int retval;

    if (VTOVCB(vp)->vcbSigWord != kHFSPlusSigWord)
        return E_NONE;

#if OVERRIDE_UNKNOWN_PERMISSIONS
	if (VTOVFS(vp)->mnt_flag & MNT_UNKNOWNPERMISSIONS) {
		return E_NONE;
	};
#endif
	
	if ((retval = hfs_owner_rights(vp, cred, p, true)) != 0)
		return (retval);
	if (cred->cr_uid) {
		if (vp->v_type != VDIR && (mode & S_ISTXT))
			return (EFTYPE);
		if (!groupmember(hp->h_meta->h_gid, cred) && (mode & ISGID))
			return (EPERM);
	}
	hp->h_meta->h_mode &= ~ALLPERMS;
	hp->h_meta->h_mode |= (mode & ALLPERMS);
	hp->h_meta->h_metaflags &= ~IN_UNSETACCESS;
	hp->h_nodeflags |= IN_CHANGE;
	return (0);
}


static int
hfs_write_access(struct vnode *vp, struct ucred *cred, struct proc *p, Boolean considerFlags)
{
    struct hfsnode *hp 			= VTOH(vp);
    ExtendedVCB	*vcb			= HTOVCB(hp);
    gid_t *gp;
    Boolean isHFSPlus;
    int retval 					= E_NONE;
    int i;

    isHFSPlus = (vcb->vcbSigWord == kHFSPlusSigWord );

    /*
     * Disallow write attempts on read-only file systems;
     * unless the file is a socket, fifo, or a block or
     * character device resident on the file system.
     */
	switch (vp->v_type) {
	  case VDIR:
      case VLNK:
      case VREG:
        if (VTOVFS(vp)->mnt_flag & MNT_RDONLY)
            return (EROFS);
        break;
      default:
		break;
 	}
 
	/* If immutable bit set, nobody gets to write it. */
    if (considerFlags && (hp->h_meta->h_pflags & IMMUTABLE))
        return (EPERM);

    /* Otherwise, user id 0 always gets access. */
    if (cred->cr_uid == 0) {
        retval = 0;
        goto Exit;
    };

    /* Otherwise, check the owner. */
    if ((retval = hfs_owner_rights(vp, cred, p, false)) == 0) {
        retval = ((hp->h_meta->h_mode & S_IWUSR) == S_IWUSR ? 0 : EACCES);
        goto Exit;
    }
 
    /* Otherwise, check the groups. */
    for (i = 0, gp = cred->cr_groups; i < cred->cr_ngroups; i++, gp++)
        if (hp->h_meta->h_gid == *gp) {
            retval = ((hp->h_meta->h_mode & S_IWGRP) == S_IWGRP ? 0 : EACCES);
 			goto Exit;
        }
 
    /* Otherwise, check everyone else. */
	retval = ((hp->h_meta->h_mode & S_IWOTH) == S_IWOTH ? 0 : EACCES);

Exit:
	return (retval);    
}



/*
 * Change the flags on a file or directory.
 * Inode must be locked before calling.
 */
static int
hfs_chflags(vp, flags, cred, p)
register struct vnode *vp;
register u_long flags;
register struct ucred *cred;
struct proc *p;
{
    register struct hfsnode *hp = VTOH(vp);
    int retval;

	if (VTOVCB(vp)->vcbSigWord == kHFSSigWord) {
		if ((retval = hfs_write_access(vp, cred, p, false)) != 0) {
			return retval;
		};
	} else if ((retval = hfs_owner_rights(vp, cred, p, true)) != 0) {
		return retval;
	};

	if (cred->cr_uid == 0) {
		if ((hp->h_meta->h_pflags & (SF_IMMUTABLE | SF_APPEND)) &&
			securelevel > 0) {
			return EPERM;
		};
		hp->h_meta->h_pflags = flags;
	} else {
		if (hp->h_meta->h_pflags & (SF_IMMUTABLE | SF_APPEND) ||
			(flags & UF_SETTABLE) != flags) {
			return EPERM;
		};
		hp->h_meta->h_pflags &= SF_SETTABLE;
		hp->h_meta->h_pflags |= (flags & UF_SETTABLE);
	}
	hp->h_meta->h_metaflags &= ~IN_UNSETACCESS;
	hp->h_nodeflags |= IN_CHANGE;

    return 0;
}


/*
 * Perform chown operation on hfsnode hp;
 * hfsnode must be locked prior to call.
 */
static int
hfs_chown(vp, uid, gid, cred, p)
register struct vnode *vp;
uid_t uid;
gid_t gid;
struct ucred *cred;
struct proc *p;
{
    register struct hfsnode *hp = VTOH(vp);
    uid_t ouid;
    gid_t ogid;
    int retval = 0;

    if (VTOVCB(vp)->vcbSigWord != kHFSPlusSigWord)
        return EOPNOTSUPP;

	if (VTOVFS(vp)->mnt_flag & MNT_UNKNOWNPERMISSIONS) {
		return E_NONE;
	};
	
    if (uid == (uid_t)VNOVAL)
        uid = hp->h_meta->h_uid;
    if (gid == (gid_t)VNOVAL)
        gid = hp->h_meta->h_gid;
    /*
     * If we don't own the file, are trying to change the owner
     * of the file, or are not a member of the target group,
     * the caller must be superuser or the call fails.
     */
    if ((cred->cr_uid != hp->h_meta->h_uid || uid != hp->h_meta->h_uid ||
         (gid != hp->h_meta->h_gid && !groupmember((gid_t)gid, cred))) &&
        (retval = suser(cred, &p->p_acflag)))
        return (retval);
    
    ogid = hp->h_meta->h_gid;
    ouid = hp->h_meta->h_uid;
	
	hp->h_meta->h_gid = gid;
    hp->h_meta->h_uid = uid;

    hp->h_meta->h_metaflags &= ~IN_UNSETACCESS;
    if (ouid != uid || ogid != gid)
        hp->h_nodeflags |= IN_CHANGE;
    if (ouid != uid && cred->cr_uid != 0)
        hp->h_meta->h_mode &= ~ISUID;
    if (ogid != gid && cred->cr_uid != 0)
        hp->h_meta->h_mode &= ~ISGID;
    return (0);
}



/*
#
#% exchange fvp		L L L
#% exchange tvp		L L L
#
 vop_exchange {
     IN struct vnode *fvp;
     IN struct vnode *tvp;
     IN struct ucred *cred;
     IN struct proc *p;
 };

 */
 /*
  * exchange is a very tricky routine, because we might have to unlock the
  * passed in vnode, and then retry locking it and all its siblings, and then
  * unlocking them in reverse.
  * Also the sibling list lock must be kept during the whole operation to
  * make sure nothing changes underneath us.
  * Also it depends on behavior of the sibling list and hash, so
  * careful if you change anything.
  */
  
static int
hfs_exchange(ap)
struct vop_exchange_args /* {
struct vnode *a_fvp;
struct vnode *a_tvp;
struct ucred *a_cred;
struct proc *a_p;
} */ *ap;
{
	struct hfsnode *from_hp, *to_hp, *nhp;
	struct hfsnode *fromFirst, *fromSecond, *toFirst, *toSecond;
	struct vnode *from_vp, *to_vp;
	struct hfsmount *hfsmp;
	u_char tmp_name[kHFSPlusMaxFileNameBytes+1];		/* 766 bytes! */
	ExtendedVCB *vcb;
	u_int32_t fromFileID, toFileID;
	u_int32_t fromParID;
	u_int32_t tmpLong;
	int retval = E_NONE;
	DBG_FUNC_NAME("exchange");
	DBG_VOP_LOCKS_DECL(2);
	DBG_VOP_LOCKS_INIT(0,ap->a_fvp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_POS);
	DBG_VOP_LOCKS_INIT(1,ap->a_tvp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_POS);

	/* Set up variables and checks */
	from_vp 	= ap->a_fvp;
    to_vp 		= ap->a_tvp;
	from_hp		= VTOH(from_vp);
	to_hp		= VTOH(to_vp);
	hfsmp		= VTOHFS(from_vp);
	vcb			= HTOVCB(from_hp);
	toFileID 	= H_FILEID(to_hp);
	fromFileID 	= H_FILEID(from_hp);
	fromParID 	= H_DIRID(from_hp);

    if (from_vp->v_mount != to_vp->v_mount) {
		DBG_VOP_LOCKS_TEST(EXDEV);
		return EXDEV;
	}

	/* Can only exchange file objects */
    if (from_vp->v_type != VREG || to_vp->v_type != VREG) {
		DBG_VOP_LOCKS_TEST(EINVAL);
		return EINVAL;
	}

	/*
	 * Lock the siblink list
	 * Check for multiple forks
	 * If there are, we would need to:
	 * 1. Unlock ourselves
	 * 3. Traverse the list in a forward order...locking all vnodes
	 * 4. Flush all buffers
	 * 5. Perform the exchange
	 * 6. Traverse the list in a reverse order...unlocking all vnodes, except orignal
	 * Notice that the sibling lock is kept during the whole operation. This quarentees
	 * that no new forks are taken off or put on
	 */
	DBG_ASSERT(H_FORKTYPE(from_hp)==kDataFork && H_FORKTYPE(to_hp)==kDataFork);	
	fromFirst = fromSecond = toFirst = toSecond = NULL;

	if (from_hp->h_meta->h_usecount > 1) {
		/*
		 * This has siblings, so remember the passed-in vnode,
		 * unlock it if it is not the 'first' sibling,
		 * and then lock the rest of the vnodes by sibling order.
		 * Notice that the passed-in vnode is not vrele(), this
		 * keeps the usecount>0, so it wont go away.
		 */
		simple_lock(&from_hp->h_meta->h_siblinglock);
		fromFirst = from_hp->h_meta->h_siblinghead.cqh_first;
		fromSecond = fromFirst->h_sibling.cqe_next;
		simple_unlock(&from_hp->h_meta->h_siblinglock);
			
		if (fromFirst == from_hp) {
        	if (vget(HTOV(fromSecond), LK_EXCLUSIVE | LK_RETRY, ap->a_p))
				fromSecond = NULL;		/* its going away */
		} else {
        	VOP_UNLOCK(HTOV(from_hp), 0, ap->a_p);
        	if (vget(HTOV(fromFirst), LK_EXCLUSIVE | LK_RETRY, ap->a_p))
				fromFirst = NULL;		/* its going away */
        	if (vget(HTOV(fromSecond), LK_EXCLUSIVE | LK_RETRY, ap->a_p))
				fromSecond = NULL;		/* its going away */
		};

	} else {
		fromFirst = from_hp;
	};

	if (to_hp->h_meta->h_usecount > 1) {

	simple_lock(&to_hp->h_meta->h_siblinglock);
		toFirst = to_hp->h_meta->h_siblinghead.cqh_first;
		toSecond = toFirst->h_sibling.cqe_next;
		simple_unlock(&to_hp->h_meta->h_siblinglock);
		
		if (toFirst == to_hp) {
        	if (vget(HTOV(toSecond), LK_EXCLUSIVE | LK_RETRY, ap->a_p))
				toSecond = NULL;		/* its going away */
		} else {
        	VOP_UNLOCK(HTOV(to_hp), 0, ap->a_p);
        	if (vget(HTOV(toFirst), LK_EXCLUSIVE | LK_RETRY, ap->a_p))
				toFirst = NULL;			/* its going away */
        	if (vget(HTOV(toSecond), LK_EXCLUSIVE | LK_RETRY, ap->a_p))
				toSecond = NULL;		/* its going away */
		};

	} else {
		toFirst = to_hp;
	};


		/* Ignore any errors, we are doing a 'best effort' on flushing */
	if (fromFirst)
		(void) vinvalbuf(HTOV(fromFirst), V_SAVE, ap->a_cred, ap->a_p, 0, 0);
	if (fromSecond)
		(void) vinvalbuf(HTOV(fromSecond), V_SAVE, ap->a_cred, ap->a_p, 0, 0);
	if (toFirst)
		(void) vinvalbuf(HTOV(toFirst), V_SAVE, ap->a_cred, ap->a_p, 0, 0);
	if (toSecond)
		(void) vinvalbuf(HTOV(toSecond), V_SAVE, ap->a_cred, ap->a_p, 0, 0);


	/* lock catalog b-tree */
	retval = hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_EXCLUSIVE, ap->a_p);
	if (retval) goto Err_Exit;

	/* lock extents b-tree iff there are overflow extents */
	/* XXX SER ExchangeFileIDs() always tries to delete the virtual extent id for exchanging files
		so we neeed the tree to be always locked.
	*/
	retval = hfs_metafilelocking(hfsmp, kHFSExtentsFileID, LK_EXCLUSIVE, ap->a_p);
	if (retval) goto Err_Exit_Relse;

	/* Do the exchange */
	retval = MacToVFSError( ExchangeFileIDs(vcb, H_NAME(from_hp), H_NAME(to_hp), H_DIRID(from_hp), H_DIRID(to_hp), H_HINT(from_hp), H_HINT(to_hp) ));

	(void) hfs_metafilelocking(hfsmp, kHFSExtentsFileID, LK_RELEASE, ap->a_p);

	if (retval != E_NONE) {
		DBG_ERR(("/tError trying to exchange: %d\n", retval));
		goto Err_Exit_Relse;
	}

	
    /* Purge the vnodes from the name */
 	if (fromFirst)
		cache_purge(HTOV(fromFirst));
	if (fromSecond)
		cache_purge(HTOV(fromSecond));
	if (toFirst)
		cache_purge(HTOV(toFirst));
	if (toSecond)
		cache_purge(HTOV(toSecond));
	
	/* Now exchange fileID, parID, name for the vnode itself */
	copystr(H_NAME(from_hp), (char*) tmp_name, strlen(H_NAME(from_hp))+1, NULL);
	hfs_chid(from_hp, toFileID, H_DIRID(to_hp), H_NAME(to_hp));
	hfs_chid(to_hp, fromFileID, fromParID, (char*) tmp_name);
	
	/* copy rest */
	tmpLong = HTOFCB(from_hp)->fcbFlags;
	HTOFCB(from_hp)->fcbFlags = HTOFCB(to_hp)->fcbFlags;
	HTOFCB(to_hp)->fcbFlags = tmpLong;

	tmpLong = from_hp->h_meta->h_crtime;
	from_hp->h_meta->h_crtime = to_hp->h_meta->h_crtime;
	to_hp->h_meta->h_crtime = tmpLong;

	tmpLong = from_hp->h_meta->h_butime;
	from_hp->h_meta->h_butime = to_hp->h_meta->h_butime;
	to_hp->h_meta->h_butime = tmpLong;

	tmpLong = from_hp->h_meta->h_atime;
	from_hp->h_meta->h_atime = to_hp->h_meta->h_atime;
	to_hp->h_meta->h_atime = tmpLong;

	tmpLong = from_hp->h_meta->h_ctime;
	from_hp->h_meta->h_ctime = to_hp->h_meta->h_ctime;
	to_hp->h_meta->h_ctime = tmpLong;

	tmpLong = from_hp->h_meta->h_gid;
	from_hp->h_meta->h_gid = to_hp->h_meta->h_gid;
	to_hp->h_meta->h_gid = tmpLong;

	tmpLong = from_hp->h_meta->h_uid;
	from_hp->h_meta->h_uid = to_hp->h_meta->h_uid;
	to_hp->h_meta->h_uid = tmpLong;

	tmpLong = from_hp->h_meta->h_pflags; 
	from_hp->h_meta->h_pflags = to_hp->h_meta->h_pflags;
	to_hp->h_meta->h_pflags = tmpLong;

	tmpLong = from_hp->h_meta->h_mode;	
	from_hp->h_meta->h_mode = to_hp->h_meta->h_mode;
	to_hp->h_meta->h_mode = tmpLong;

	tmpLong = from_hp->h_meta->h_rdev;	
	from_hp->h_meta->h_rdev = to_hp->h_meta->h_rdev;
	to_hp->h_meta->h_rdev = tmpLong;

	tmpLong = from_hp->h_meta->h_size;	
	from_hp->h_meta->h_size = to_hp->h_meta->h_size;
	to_hp->h_meta->h_size = tmpLong;

	

Err_Exit_Relse:

	/* unlock catalog b-tree */
	(void) hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_RELEASE, ap->a_p);


Err_Exit:


	/* XXX SER
	 * At this point, the vnodes' data is switched, but are on the old hash list.
	 * so move them to the right bucket. This couldnt be done until now, because the h_siblinglock
	 * was being held.
	 * Scenario:
	 * A fork is trying to be added while exchanging...It got the hash lock,
	 * but is waiting for the h_siblinglock. So we cannot try get the hash lock
	 * until we release h_siblinglock, so it could continue, so it adds to the sibling list
	 * and at the old place, so hfs_vhashmove has to move all vnodes with the old file id.
	 * Not very pretty, becarefull that this works ok
	 * Scenario 2:
	 * Same as the above, but before the move is made (like at this very spot), the new vnode
	 * is added and a vget is requested for that new vnode, it would have old data
	 *	WE MIGHT NEED TO LOCK THE HASH BECAUSE OF THIS !!!
	 * Scenario 3:
	 * Hey! Same as above, but it is added after all the moving
	 * So now there is a vnode with the old data, on the old hash...it will become
	 * lost next time that a vget()  is done
	 *
	 * XXX SER A solution might be to NOT move the hash, but the data (extents) or the
	 * opposite that we are doing now
	 */
	hfs_vhashmove(from_hp, fromFileID);
	hfs_vhashmove(to_hp, toFileID);


#if HFS_DIAGNOSTIC
	if (fromFirst)
		debug_check_vnode(HTOV(fromFirst), 0);
	if (fromSecond)
		debug_check_vnode(HTOV(fromSecond), 0);
	if (toFirst)
		debug_check_vnode(HTOV(toFirst),  0);
	if (toSecond)
		debug_check_vnode(HTOV(toSecond),  0);
#endif


	/* Unlock any forks, and the sibling list */
    if (to_hp->h_meta->h_usecount > 1) {
    	if (to_hp == toFirst) {
    		if (toSecond)
    			vput(HTOV(toSecond));
    	} else {
    		if (toSecond)
                vrele(HTOV(toSecond));		/* decrement,  return it locked */
    		if (toFirst)
    			vput(HTOV(toFirst));
     	}
	}
    if (from_hp->h_meta->h_usecount > 1) {
    	if (from_hp == fromFirst) {
    		if (fromSecond)
    			vput(HTOV(fromSecond));
    	} else {
    		if (fromSecond)
                vrele(HTOV(fromSecond));		/* decrement,  return it locked */
    		if (fromFirst)
    			vput(HTOV(fromFirst));
     	}
	}

	DBG_VOP_LOCKS_TEST(retval);
	return (retval);
}


/*
 * Change a vnode's file id, parent id and name
 * 
 * Assumes the vnode is locked and is of type VREG
 */
static void
hfs_chid(struct hfsnode *hp, u_int32_t fid, u_int32_t pid, char* name)
{
	DBG_ASSERT(HTOV(hp)->v_type == VREG);

	H_HINT(hp) = 0;
	H_FILEID(hp) = fid;					/* change h_nodeID */
	H_DIRID(hp) = pid;
	
	hfs_set_metaname(name, hp->h_meta, HTOHFS(hp));


}


/*

#% fsync	vp	L L L
#
 vop_fsync {
     IN struct vnode *vp;
     IN struct ucred *cred;
     IN int waitfor;
     IN struct proc *p;

     */


static int
hfs_fsync(ap)
struct vop_fsync_args /* {
    struct vnode *a_vp;
    struct ucred *a_cred;
    int a_waitfor;
    struct proc *a_p;
} */ *ap;
{
    struct vnode 		*vp = ap->a_vp ;
    struct hfsnode 		*hp	= VTOH(vp);
    int					retval = 0;
    register struct buf *bp;
    struct timeval 		tv;
    struct buf 			*nbp;
    int 				s;

    DBG_FUNC_NAME("fsync");
    DBG_VOP_LOCKS_DECL(1);
    DBG_VOP_PRINT_FUNCNAME();DBG_VOP_CONT(("  "));
    DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));
    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_ZERO);
    DBG_HFS_NODE_CHECK(ap->a_vp);
	
#if HFS_DIAGNOSTIC
    DBG_ASSERT(*((int*)&vp->v_interlock) == 0);
#endif


    /*
     * First of all, write out any clusters.
     */
    cluster_push(vp);

    /*
     * Flush all dirty buffers associated with a vnode.
     */
loop:
    s = splbio();
    for (bp = vp->v_dirtyblkhd.lh_first; bp; bp = nbp) {
        nbp = bp->b_vnbufs.le_next;
        if ((bp->b_flags & B_BUSY))
            continue;
        if ((bp->b_flags & B_DELWRI) == 0)
            panic("hfs_fsync: not dirty");
        bremfree(bp);
        bp->b_flags |= B_BUSY;
        bp->b_flags &= ~B_LOCKED;	/* Clear flag, should only be set on meta files */
        splx(s);
        /*
         * Wait for I/O associated with indirect blocks to complete,
         * since there is no way to quickly wait for them below.
         */
        DBG_VOP(("\t\t\tFlushing out phys block %d == log block %d\n", bp->b_blkno, bp->b_lblkno));
        if (bp->b_vp == vp || ap->a_waitfor == MNT_NOWAIT) {
            (void) bawrite(bp);
        } else {
            (void) VOP_BWRITE(bp);
	}
        goto loop;
    }
    if (vp->v_flag & VHASDIRTY)
	ubc_pushdirty(vp);

    if (ap->a_waitfor == MNT_WAIT) {
        while (vp->v_numoutput) {
            vp->v_flag |= VBWAIT;
            tsleep((caddr_t)&vp->v_numoutput, PRIBIO + 1, "hfs_fsync", 0);
        }

        /* I have seen this happen for swapfile. So it is safer to
         * check for dirty buffers again.  --Umesh
         */
        if (vp->v_dirtyblkhd.lh_first || (vp->v_flag & VHASDIRTY)) {
            vprint("hfs_fsync: dirty", vp);
            splx(s);
            goto loop;
        }
    }
    splx(s);

#if HFS_DIAGNOSTIC
    DBG_ASSERT(*((int*)&vp->v_interlock) == 0);
#endif

   	tv = time;
	if ((vp->v_flag & VSYSTEM) && (hp->fcbBTCBPtr!=NULL))
		BTSetLastSync(HTOFCB(hp), tv.tv_sec);

	if (H_FORKTYPE(hp) != kSysFile) {
    	retval = VOP_UPDATE(ap->a_vp, &tv, &tv, ap->a_waitfor == MNT_WAIT);

    	if (retval != E_NONE) {
        	DBG_ERR(("%s: FLUSH FAILED: %s\n", funcname, H_NAME(hp)));
    	}
    }
	else
		hp->h_nodeflags &= ~(IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE);

    if (ap->a_waitfor == MNT_WAIT) {
      DBG_ASSERT(vp->v_dirtyblkhd.lh_first == NULL);
    };
    DBG_VOP_LOCKS_TEST(retval);
    DBG_ASSERT(*((int*)&vp->v_interlock) == 0);
    return (retval);
}


int
hfs_fsync_transaction(struct vnode *vp)
{
    struct hfsnode 		*hp = VTOH(vp);
    register struct buf         *bp;
    struct timeval 		tv;
    struct buf 			*nbp;
    int 			s;

    /*
     * Flush all dirty buffers associated with a vnode.
     */
loop:
    s = splbio();

    for (bp = vp->v_dirtyblkhd.lh_first; bp; bp = nbp) {
        nbp = bp->b_vnbufs.le_next;
        if ((bp->b_flags & B_BUSY))
            continue;
        if ((bp->b_flags & B_DELWRI) == 0)
            panic("hfs_fsync: not dirty");
	if ( !(bp->b_flags & B_LOCKED))
	    continue;

        bremfree(bp);
        bp->b_flags |= B_BUSY;
        bp->b_flags &= ~B_LOCKED;	/* Clear flag, should only be set on meta files */
        splx(s);

	(void) bawrite(bp);

        goto loop;
    }
    splx(s);

    tv = time;
	if ((vp->v_flag & VSYSTEM) && (hp->fcbBTCBPtr!=NULL))
		(void) BTSetLastSync(VTOFCB(vp), tv.tv_sec);
    hp->h_nodeflags &= ~(IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE);

    return 0;
}

/*

#% remove	dvp	L U U
#% remove	vp	L U U
#
 vop_remove {
     IN WILLRELE struct vnode *dvp;
     IN WILLRELE struct vnode *vp;
     IN struct componentname *cnp;

     */

int
hfs_remove(ap)
struct vop_remove_args /* {
    struct vnode *a_dvp;
    struct vnode *a_vp;
    struct componentname *a_cnp;
} */ *ap;
{
        struct vnode *vp = ap->a_vp;
        struct vnode *dvp = ap->a_dvp;
        struct hfsnode *hp = VTOH(ap->a_vp);
        struct hfsmount *hfsmp = HTOHFS(hp);
        struct proc *p = current_proc();
        struct timeval tv;
        int retval, use_count;
        int filebusy = 0;
        DBG_FUNC_NAME("remove");
        DBG_VOP_LOCKS_DECL(2);
        DBG_VOP_PRINT_FUNCNAME();
        DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);
        DBG_VOP_PRINT_CPN_INFO(ap->a_cnp);DBG_VOP_CONT(("\n"));
        DBG_VOP_LOCKS_INIT(0,ap->a_dvp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
        DBG_VOP_LOCKS_INIT(1,ap->a_vp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);

        retval = E_NONE;

        if ((hp->h_meta->h_pflags & (IMMUTABLE | APPEND)) ||
            (VTOH(dvp)->h_meta->h_pflags & APPEND)) {
                retval = EPERM;
                goto out;
        }

		if (vp->v_usecount > 1) {
			/*
			 * the namei done for the rename took a reference on the
			 * vnode. Hence set 1 in the tookref parameter 
			 * of ubc_isinuse().
			 */
			if(UBCISVALID(vp) && !ubc_isinuse(vp, 1))
				goto hfs_nobusy;
			if ((ap->a_cnp->cn_flags & NODELETEBUSY)
				|| (hfsmp->hfs_private_metadata_dir == 0)) {
				/* Carbon semantics prohibits deleting busy files */
				retval = EBUSY;
				goto out;
			} else
				filebusy = 1;
		}

hfs_nobusy:

                tv = time;					/* Done here, so all times are the same */

        /* Check other siblings for in use also */
        /* Uncache everything and make sure no other usecount */
        /*
         * This assumes the presence of the most 1 sibling
         *
         * a. loop through the siblings looking for another
         * b. If we find ourselves...skip it
         * If there was a sibling:
         * a. Check for a positve usecount
         * b. uncache any pages
         * c. Write out and memory changes
         * The idea is to keep the h_siblinglock as little as possible
         */
        if (hp->h_meta->h_usecount > 1) {
                struct vnode *sib_vp = NULL;
                struct hfsnode *nhp;

                DBG_ASSERT(hp->h_meta->h_siblinghead.cqh_first &&
                           (hp->h_meta->h_siblinghead.cqh_first != hp->h_meta->h_siblinghead.cqh_last));
                DBG_ASSERT(H_FORKTYPE(hp)==kDataFork || H_FORKTYPE(hp)==kRsrcFork);

                /* Loop through all siblings, skipping ourselves */
                simple_lock(&hp->h_meta->h_siblinglock);
                CIRCLEQ_FOREACH(nhp, &hp->h_meta->h_siblinghead, h_sibling) {
                        if (nhp == hp)		/* skip ourselves */
                                continue;
                        sib_vp = HTOV(nhp);
                };
                simple_unlock(&hp->h_meta->h_siblinglock);

                /* Check to see if the other fork is in use */
                DBG_ASSERT(sib_vp != NULL);
                simple_lock(&sib_vp->v_interlock);
                use_count = sib_vp->v_usecount;
                simple_unlock(&sib_vp->v_interlock);
                if (use_count > 0) {
					/*
					 * This is a sibling vnode and we did not take 
					 * a reference on it.
					 * Hence set 0 in the tookref parameter 
					 * of ubc_isinuse().
					 */
					if(UBCISVALID(sib_vp) && !ubc_isinuse(sib_vp, 0))
						goto hfs_nobusy2;
					if ((ap->a_cnp->cn_flags & NODELETEBUSY)
						|| (hfsmp->hfs_private_metadata_dir == 0)) {
						/* Carbon semantics prohibits deleting busy files */
						retval = EBUSY;
						goto out;
					} else
						filebusy = 1;
                }	/* use_count > 0 */

hfs_nobusy2:	

                /* The only error that vget returns is when the vnode is going away, so ignore the vnode */
                if (vget(sib_vp, LK_EXCLUSIVE | LK_RETRY, p) == 0) {
                        /*
                        * XXX SER An intelligient person would ask, why flush out changes
                        * that are going to be deleted? See the next comment.
                        */
                        if ((VTOH(sib_vp)->h_nodeflags & IN_MODIFIED) || (VTOFCB(sib_vp)->fcbFlags
                               		 & fcbModifiedMask)) {
                                DBG_ASSERT((VTOH(sib_vp)->h_nodeflags & IN_MODIFIED) != 0);
                                VOP_UPDATE(sib_vp, &tv, &tv, 0);
                        };

                        /* Invalidate the buffers, ignore the results */
                        (void) vinvalbuf(sib_vp, 0, NOCRED, p, 0, 0);

                        vput(sib_vp);
                };	/* vget() */
    };	/* h_use_count > 1 */

        /*
        * remove the entry from the namei cache:
        * We do it early before any linking/busy file wierdness, make sure the
        * original is gone
        */
        cache_purge(vp);
        
	/* Flush out any catalog changes */
	/* XXX SER: This is a hack, becasue hfsDelete reads the data from the disk
	 * and not from memory which is more correct
	 */
	if ((hp->h_nodeflags & IN_MODIFIED) || (HTOFCB(hp)->fcbFlags & fcbModifiedMask))
		{
                DBG_ASSERT((hp->h_nodeflags & IN_MODIFIED) != 0);
                VOP_UPDATE(vp, &tv, &tv, 0);
                }

	/* lock catalog b-tree */
	retval = hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_EXCLUSIVE, p);
	if (retval != E_NONE) {
                retval = EBUSY;
                goto out;
        }

    /*
     * After this point, any errors must goto out2, so the Catalog Tree gets unlocked
     */

#if HFS_HARDLINKS
	/*
         * Multi-linked files just need their link node deleted from the catalog
         */
	if (hp->h_meta->h_metaflags & IN_DATANODE) {

                if ((ap->a_cnp->cn_flags & HASBUF) == 0 ||
                    ap->a_cnp->cn_nameptr[0] == '\0') {
                        retval = ENOENT;	/* name missing */
                        goto out2;
                }

                /* lock extents b-tree (also protects volume bitmap) */
                retval = hfs_metafilelocking(hfsmp, kHFSExtentsFileID, LK_EXCLUSIVE, p);
                if (retval != E_NONE) {
                        retval = EBUSY;
                        goto out2;						/* unlock catalog b-tree on the way out */
                }

                retval = hfsDelete (HTOVCB(hp), H_FILEID(VTOH(dvp)),
                                    ap->a_cnp->cn_nameptr, TRUE, H_HINT(hp));

                (void) hfs_metafilelocking(hfsmp, kHFSExtentsFileID, LK_RELEASE, p);

                if (retval != 0)
                        goto out2;

                hp->h_meta->h_metaflags |=  IN_NOEXISTS;
                hp->h_nodeflags |= IN_CHANGE;
                if (--hp->h_meta->h_nlink < 1)
                        hp->h_meta->h_metaflags |= IN_DELETED;

                /* name and parent fields are no longer valid so invalidate them */
                H_DIRID(hp) = kUnknownID;
                hfs_set_metaname("\0", hp->h_meta, HTOHFS(hp));

                if ((ap->a_cnp->cn_flags & (HASBUF | SAVENAME)) == (HASBUF | SAVENAME))
                        FREE_ZONE(ap->a_cnp->cn_pnbuf, ap->a_cnp->cn_pnlen, M_NAMEI);

                goto out2;	/* link deleted, all done */
        }
#endif

	/*
         * To make the HFS filesystem follow UFS unlink semantics, a remove of
         * an active vnode is translated to a move/rename so the file appears
         * deleted. Later, the file is removed by hfs_inactive on the hfsnode.
         */
	if (filebusy) {
                UInt32 hint = H_HINT(hp);
                char nodeName[32];

                MAKE_DELETED_NAME(nodeName, H_FILEID(hp));

                retval = hfsMoveRename (HTOVCB(hp), H_DIRID(hp), H_NAME(hp),
                                        hfsmp->hfs_private_metadata_dir, nodeName, &hint);
                if (retval) goto out2;

                hp->h_meta->h_metaflags |= IN_DELETED;
                hp->h_nodeflags |= IN_CHANGE;

                /* update name so Catalog lookups succeed */
                H_HINT(hp) = hint;
                H_DIRID(hp) = hfsmp->hfs_private_metadata_dir;
                hfs_set_metaname(nodeName, hp->h_meta, HTOHFS(hp));

                goto out2;	/* all done, unlock the catalog */
        }

	/* unlock the Catalog */
	(void) hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_RELEASE, p);

	/* Invalidate the buffers */
	if ((retval= vinvalbuf(vp, 0, NOCRED, p, 0, 0)))
		goto out;
	
	if(UBCINFOEXISTS(vp))
		(void)ubc_setsize(vp, (off_t)0);

	
	/* lock catalog b-tree */
	retval = hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_EXCLUSIVE, p);
	if (retval != E_NONE) {
		retval = EBUSY;
		goto out;
	}
	/* lock extents b-tree (also protects volume bitmap) */
	retval = hfs_metafilelocking(hfsmp, kHFSExtentsFileID, LK_EXCLUSIVE, p);
	if (retval != E_NONE) {
                retval = EBUSY;
                goto out2;						/* unlock catalog b-tree on the way out */
        }

	/* remove entry from catalog and free any blocks used */
	retval = hfsDelete (HTOVCB(hp), H_DIRID(hp), H_NAME(hp), TRUE, H_HINT(hp));

	/* Clean up */
	(void) hfs_metafilelocking(hfsmp, kHFSExtentsFileID, LK_RELEASE, p);
	(void) hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_RELEASE, p);

        if (retval != 0)
                goto out;

	hp->h_meta->h_metaflags |=  IN_NOEXISTS;
	hp->h_meta->h_mode = 0;	/* Makes the node go away...see inactive */
	/* clear the block mappings */
	hp->fcbPLen = (u_int64_t)0;
	bzero(&hp->fcbExtents, sizeof(HFSPlusExtentRecord));

	VTOH(dvp)->h_nodeflags |= IN_CHANGE | IN_UPDATE;

	if (dvp == vp) {
                vrele(vp);
        } else {
                vput(vp);
        };

	vput(dvp);
	DBG_VOP_LOCKS_TEST(retval);

	if (UBCINFOEXISTS(vp)) {
                (void) ubc_uncache(vp);
                ubc_release(vp);
                /* WARNING vp may not be valid after this */
        }
	return (retval);

out2:
	(void) hfs_metafilelocking(hfsmp, kHFSCatalogFileID, LK_RELEASE, p);

out:;

	if (! retval)
                VTOH(dvp)->h_nodeflags |= IN_CHANGE | IN_UPDATE;

	if (dvp == vp) {
                vrele(vp);
        } else {
                vput(vp);
        };

	vput(dvp);
	DBG_VOP_LOCKS_TEST(retval);
	return (retval);
}


/*

#% rename	sourcePar_vp	U U U
#% rename	source_vp		U U U
#% rename	targetPar_vp	L U U
#% rename	target_vp		X U U
#
 vop_rename {
     IN WILLRELE struct vnode *sourcePar_vp;
     IN WILLRELE struct vnode *source_vp;
     IN struct componentname *source_cnp;
     IN WILLRELE struct vnode *targetPar_vp;
     IN WILLRELE struct vnode *target_vp;
     IN struct componentname *target_cnp;


     */
/*
* On entry:
*	source's parent directory is unlocked
*	source file or directory is unlocked
*	destination's parent directory is locked
*	destination file or directory is locked if it exists
*
* On exit:
*	all denodes should be released
*
*/

static int
hfs_rename(ap)
struct vop_rename_args  /* {
    struct vnode *a_fdvp;
    struct vnode *a_fvp;
    struct componentname *a_fcnp;
    struct vnode *a_tdvp;
    struct vnode *a_tvp;
    struct componentname *a_tcnp;
} */ *ap;
{
	struct vnode			*target_vp = ap->a_tvp;
	struct vnode			*targetPar_vp = ap->a_tdvp;
	struct vnode			*source_vp = ap->a_fvp;
	struct vnode			*sourcePar_vp = ap->a_fdvp;
	struct componentname	*target_cnp = ap->a_tcnp;
	struct componentname	*source_cnp = ap->a_fcnp;
	struct proc				*p = source_cnp->cn_proc;
	struct hfsnode			*target_hp, *targetPar_hp, *source_hp, *sourcePar_hp;
	u_int32_t				oldparent = 0, newparent = 0;
	int						doingdirectory = 0;
	int						retval = 0;
	struct timeval			tv;
	struct hfsCatalogInfo 	catInfo;
	DBG_VOP_LOCKS_DECL(4);

    DBG_FUNC_NAME("rename");DBG_VOP_PRINT_FUNCNAME();DBG_VOP_CONT(("\n"));
    DBG_VOP_CONT(("\t"));DBG_VOP_CONT(("Source:\t"));DBG_VOP_PRINT_VNODE_INFO(ap->a_fvp);DBG_VOP_CONT(("\n"));
    DBG_VOP_CONT(("\t"));DBG_VOP_CONT(("SourcePar: "));DBG_VOP_PRINT_VNODE_INFO(ap->a_fdvp);DBG_VOP_CONT(("\n"));
    DBG_VOP_CONT(("\t"));DBG_VOP_CONT(("Target:\t"));DBG_VOP_PRINT_VNODE_INFO(ap->a_tvp);DBG_VOP_CONT(("\n"));
    DBG_VOP_CONT(("\t"));DBG_VOP_CONT(("TargetPar: "));DBG_VOP_PRINT_VNODE_INFO(ap->a_tdvp);DBG_VOP_CONT(("\n"));
    DBG_VOP_CONT(("\t"));DBG_VOP_CONT(("SourceName:\t"));DBG_VOP_PRINT_CPN_INFO(ap->a_fcnp);DBG_VOP_CONT(("\n"));
    DBG_VOP_CONT(("\t"));DBG_VOP_CONT(("TargetName:\t"));DBG_VOP_PRINT_CPN_INFO(ap->a_tcnp);DBG_VOP_CONT(("\n"));
    DBG_VOP_LOCKS_INIT(0,ap->a_fdvp, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
    DBG_VOP_LOCKS_INIT(1,ap->a_fvp, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
    DBG_VOP_LOCKS_INIT(2,ap->a_tdvp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
    DBG_VOP_LOCKS_INIT(3,ap->a_tvp, VOPDBG_LOCKNOTNIL, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
    WRITE_CK(ap->a_fdvp, funcname);
    DBG_HFS_NODE_CHECK(ap->a_fdvp);
    DBG_HFS_NODE_CHECK(ap->a_tdvp);

#if HFS_DIAGNOSTIC
    if ((target_cnp->cn_flags & HASBUF) == 0 ||
        (source_cnp->cn_flags & HASBUF) == 0)
        panic("hfs_rename: no name");
#endif

	DBG_ASSERT((ap->a_fdvp->v_type == VDIR) && (ap->a_tdvp->v_type == VDIR));
	target_hp = targetPar_hp = source_hp = sourcePar_hp = 0;

	/*
	 * Check for cross-device rename.
	 */
	if ((source_vp->v_mount != targetPar_vp->v_mount) ||
		(target_vp && (source_vp->v_mount != target_vp->v_mount))) {
		retval = EXDEV;
		goto abortit;
	}

	/*
	 * Check for access permissions
	 */
	if (target_vp && ((VTOH(target_vp)->h_meta->h_pflags & (IMMUTABLE | APPEND)) ||
					  (VTOH(targetPar_vp)->h_meta->h_pflags & APPEND))) {
		retval = EPERM;
		goto abortit;
	}

	if ((retval = vn_lock(source_vp, LK_EXCLUSIVE, p)))
		goto abortit;

	sourcePar_hp = VTOH(sourcePar_vp);
	source_hp = VTOH(source_vp);
	oldparent = H_FILEID(sourcePar_hp);
	if ((source_hp->h_meta->h_pflags & (IMMUTABLE | APPEND)) || (sourcePar_hp->h_meta->h_pflags & APPEND)) {
		VOP_UNLOCK(source_vp, 0, p);
		retval = EPERM;
		goto abortit;
	}

	/*
	 * Be sure we are not renaming ".", "..", or an alias of ".". This
	 * leads to a crippled directory tree.	It's pretty tough to do a
	 * "ls" or "pwd" with the "." directory entry missing, and "cd .."
	 * doesn't work if the ".." entry is missing.
	 */
	if ((source_hp->h_meta->h_mode & IFMT) == IFDIR) {
		if ((source_cnp->cn_namelen == 1 && source_cnp->cn_nameptr[0] == '.')
			|| sourcePar_hp == source_hp
			|| (source_cnp->cn_flags&ISDOTDOT)
			|| (source_hp->h_nodeflags & IN_RENAME)) {
			VOP_UNLOCK(source_vp, 0, p);
			retval = EINVAL;
			goto abortit;
		}
		source_hp->h_nodeflags |= IN_RENAME;
		doingdirectory = TRUE;
	}

    /*
     *
     * >>>> Transit between abort and bad <<<<
     *
     */

    targetPar_hp = VTOH(targetPar_vp);
    if (target_vp)
    	target_hp = VTOH(target_vp);
    else
    	DBG_ASSERT(target_hp == NULL);

    newparent = H_FILEID(targetPar_hp);

	/* Test to make sure we are not crossing devices */
	/* XXX SER Is this necesary, does catalog manager take care of this? */
	if (target_vp) {
		if (H_DEV(target_hp) != H_DEV(targetPar_hp) || H_DEV(target_hp) != H_DEV(source_hp))
			panic("rename: EXDEV");
	}
	 else {
		if (H_DEV(targetPar_hp) != H_DEV(source_hp))
			panic("rename: EXDEV");
	};
	
    retval = VOP_ACCESS(source_vp, VWRITE, target_cnp->cn_cred, target_cnp->cn_proc);
    if (doingdirectory && (newparent != oldparent)) {
        if (retval)		/* write access check above */
            goto bad;
    }
	retval = 0;		/* Reset value from above, we dont care about it anymore */
	
	/*
	 * If the destination exists, then be sure its type (file or dir)
	 * matches that of the source.	And, if it is a directory make sure
	 * it is empty.	 Then delete the destination.
	 */
	if (target_vp) {

        /*
         * If the parent directory is "sticky", then the user must
         * own the parent directory, or the destination of the rename,
         * otherwise the destination may not be changed (except by
         * root). This implements append-only directories.
         */
        if ((targetPar_hp->h_meta->h_mode & S_ISTXT) && (target_cnp->cn_cred->cr_uid != 0) &&
            target_cnp->cn_cred->cr_uid != targetPar_hp->h_meta->h_uid &&
            target_cnp->cn_cred->cr_uid != target_hp->h_meta->h_uid) {
            retval = EPERM;
            goto bad;
        }

		/*
		 * VOP_REMOVE will vput targetPar_vp so we better bump 
		 * its ref count and relockit, always set target_vp to
		 * NULL afterwards to indicate that were done with it.
		 */
		VREF(targetPar_vp);

		cache_purge(target_vp);
            
#if HFS_HARDLINKS
		target_cnp->cn_flags &= ~SAVENAME;
#endif
		
		retval = VOP_REMOVE(targetPar_vp, target_vp, target_cnp);
		(void) vn_lock(targetPar_vp, LK_EXCLUSIVE | LK_RETRY, p);

		target_vp = NULL;
		target_hp = NULL;		
		
		if (retval) goto bad;

	};


	if (newparent != oldparent)
		vn_lock(sourcePar_vp, LK_EXCLUSIVE | LK_RETRY, p);

	/* remove the existing entry from the namei cache: */
	cache_purge(source_vp);

	INIT_CATALOGDATA(&catInfo.nodeData, 0);

	/* lock catalog b-tree */
	retval = hfs_metafilelocking(VTOHFS(source_vp), kHFSCatalogFileID, LK_EXCLUSIVE, p);
	if (retval) {
		if (newparent != oldparent)		/* unlock the lock we just got */
			VOP_UNLOCK(sourcePar_vp, 0, p);
		 goto bad;
 	};
	
	/* use source_cnp instead of H_NAME(source_hp) in case source is a hard link */
	retval = hfsMoveRename( HTOVCB(source_hp), H_DIRID(source_hp), source_cnp->cn_nameptr,
							H_FILEID(VTOH(targetPar_vp)), target_cnp->cn_nameptr, &H_HINT(source_hp));

	if (retval == 0) {	
	    /* Look up the catalog entry just renamed since it might have been auto-decomposed */
	    catInfo.hint = H_HINT(source_hp);
	    retval = hfs_getcatalog(HTOVCB(source_hp), H_FILEID(targetPar_hp), target_cnp->cn_nameptr, target_cnp->cn_namelen, &catInfo);
	}

	/* unlock catalog b-tree */
	(void) hfs_metafilelocking(VTOHFS(source_vp), kHFSCatalogFileID, LK_RELEASE, p);

	if (newparent != oldparent)
		VOP_UNLOCK(sourcePar_vp, 0, p);

	if (retval)  goto bad;

	H_DIRID(source_hp) = H_FILEID(targetPar_hp);

	hfs_name_CatToMeta(&catInfo.nodeData, source_hp->h_meta);
	
	CLEAN_CATALOGDATA(&catInfo.nodeData);

	source_hp->h_nodeflags &= ~IN_RENAME;


	/*
	 * Timestamp both parent directories.
	 * Note that if this is a rename within the same directory,
	 * (where targetPar_hp == sourcePar_hp)
	 * the code below is still safe and correct.
	 */
	targetPar_hp->h_nodeflags |= IN_UPDATE;
	sourcePar_hp->h_nodeflags |= IN_UPDATE;
	tv = time;
	HFSTIMES(targetPar_hp, &tv, &tv);
	HFSTIMES(sourcePar_hp, &tv, &tv);

	vput(targetPar_vp);
	vrele(sourcePar_vp);
	vput(source_vp);

	DBG_VOP_LOCKS_TEST(retval);
	if (retval != E_NONE) {
		DBG_VOP_PRINT_FUNCNAME();DBG_VOP_CONT(("\tReturning with error %d\n",retval));
	}
	return (retval);

bad:;
	if (retval && doingdirectory)
		source_hp->h_nodeflags &= ~IN_RENAME;

    if (targetPar_vp == target_vp)
	    vrele(targetPar_vp);
    else
	    vput(targetPar_vp);

    if (target_vp)
	    vput(target_vp);

	vrele(sourcePar_vp);

    if (VOP_ISLOCKED(source_vp))
        vput(source_vp);
	else
    	vrele(source_vp);

    DBG_VOP_LOCKS_TEST(retval);
    if (retval != E_NONE) {
        DBG_VOP_PRINT_FUNCNAME();DBG_VOP_CONT(("\tReturning with error %d\n",retval));
    }
    return (retval);

abortit:;

    VOP_ABORTOP(targetPar_vp, target_cnp); /* XXX, why not in NFS? */

    if (targetPar_vp == target_vp)
	    vrele(targetPar_vp);
    else
	    vput(targetPar_vp);

    if (target_vp)
	    vput(target_vp);

    VOP_ABORTOP(sourcePar_vp, source_cnp); /* XXX, why not in NFS? */

	vrele(sourcePar_vp);
    vrele(source_vp);

    DBG_VOP_LOCKS_TEST(retval);
    if (retval != E_NONE) {
        DBG_VOP_PRINT_FUNCNAME();DBG_VOP_CONT(("\tReturning with error %d\n",retval));
    }
    return (retval);
}



/*
 * Mkdir system call
#% mkdir	dvp	L U U
#% mkdir	vpp	- L -
#
 vop_mkdir {
     IN WILLRELE struct vnode *dvp;
     OUT struct vnode **vpp;
     IN struct componentname *cnp;
     IN struct vattr *vap;

     We are responsible for freeing the namei buffer,
	 it is done in hfs_makenode()
*/

int
hfs_mkdir(ap)
struct vop_mkdir_args /* {
    struct vnode *a_dvp;
    struct vnode **a_vpp;
    struct componentname *a_cnp;
    struct vattr *a_vap;
} */ *ap;
{
	struct proc		*p = current_proc();
	int				retval;
	int				mode = MAKEIMODE(ap->a_vap->va_type, ap->a_vap->va_mode);

	DBG_FUNC_NAME("mkdir");
	DBG_VOP_LOCKS_DECL(2);
	DBG_VOP_PRINT_FUNCNAME();
	DBG_VOP_PRINT_VNODE_INFO(ap->a_dvp);
	DBG_VOP_PRINT_CPN_INFO(ap->a_cnp);DBG_VOP_CONT(("\n"));

	DBG_VOP_LOCKS_INIT(0,ap->a_dvp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
	DBG_VOP_LOCKS_INIT(1,*ap->a_vpp, VOPDBG_IGNORE, VOPDBG_LOCKED, VOPDBG_IGNORE, VOPDBG_POS);

	DBG_VOP(("%s: parent 0x%x (%s)	ap->a_cnp->cn_nameptr %s\n", funcname, (u_int)VTOH(ap->a_dvp), H_NAME(VTOH(ap->a_dvp)), ap->a_cnp->cn_nameptr));
	WRITE_CK( ap->a_dvp, funcname);
	DBG_HFS_NODE_CHECK(ap->a_dvp);
	DBG_ASSERT(ap->a_dvp->v_type == VDIR);

	/* Create the vnode */
    DBG_ASSERT((ap->a_cnp->cn_flags & SAVESTART) == 0);
	retval = hfs_makenode(mode, 0, ap->a_dvp, ap->a_vpp, ap->a_cnp, p);
    DBG_VOP_UPDATE_VP(1, *ap->a_vpp);

    if (retval != E_NONE) {
        DBG_ERR(("%s: hfs_makenode FAILED: %s, %s\n", funcname, ap->a_cnp->cn_nameptr, H_NAME(VTOH(ap->a_dvp))));
        DBG_VOP_LOCKS_TEST(retval);
        return (retval);		
    }

    DBG_VOP_LOCKS_TEST(E_NONE);
    return (E_NONE);
}

/*
 * Rmdir system call.
#% rmdir	dvp	L U U
#% rmdir	vp	L U U
#
 vop_rmdir {
     IN WILLRELE struct vnode *dvp;
     IN WILLRELE struct vnode *vp;
     IN struct componentname *cnp;

     */

int
hfs_rmdir(ap)
struct vop_rmdir_args /* {
    struct vnode *a_dvp;
    struct vnode *a_vp;
    struct componentname *a_cnp;
} */ *ap;
{
    struct vnode *vp = ap->a_vp;
    struct vnode *dvp = ap->a_dvp;
    struct hfsnode *hp = VTOH(vp);
    struct proc *p = current_proc();
    int retval;
    DBG_FUNC_NAME("rmdir");
    DBG_VOP_LOCKS_DECL(2);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP(("\tParent: "));DBG_VOP_PRINT_VNODE_INFO(ap->a_dvp);DBG_VOP_CONT(("\n"));
    DBG_VOP(("\tTarget: "));DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));
    DBG_VOP(("\tTarget Name: "));DBG_VOP_PRINT_CPN_INFO(ap->a_cnp);DBG_VOP_CONT(("\n"));

    DBG_VOP_LOCKS_INIT(0,ap->a_dvp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
    DBG_VOP_LOCKS_INIT(1,ap->a_vp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);

    if (dvp == vp) {
        vrele(vp);
        vput(vp);
        DBG_VOP_LOCKS_TEST(EINVAL);
        return (EINVAL);
    }
	
	/*
	 * HFS differs from UFS here in that we don't allow removing
	 * a directory that in use by others - even if its empty.
	 *
	 * In the future we might want to allow this just like we do
	 * for files (by renaming the busy directory).
	 */
#if 0
    if (vp->v_usecount > 1) {
        DBG_ERR(("%s: dir is busy, usecount is %d\n", funcname, vp->v_usecount ));
		retval = EBUSY;
		goto Err_Exit;
    }
#endif
    /* remove the entry from the namei cache: */
    cache_purge(vp);

	/* lock catalog b-tree */
	retval = hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_EXCLUSIVE, p);
	if (retval != E_NONE) {
		goto Err_Exit;
	}

	/* remove entry from catalog */
    retval = hfsDelete (HTOVCB(hp), H_DIRID(hp), H_NAME(hp), FALSE, H_HINT(hp));

	/* unlock catalog b-tree */
	(void) hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_RELEASE, p);

	if (! retval) {
		VTOH(dvp)->h_nodeflags |= IN_CHANGE | IN_UPDATE;	/* Set the parent to be updated */
        hp->h_meta->h_mode = 0;								/* Makes the vnode go away...see inactive */
		hp->h_meta->h_metaflags |= IN_NOEXISTS;
    }

Err_Exit:;
    if (dvp != 0) 
		vput(dvp);
    vput(vp);

    DBG_VOP_LOCKS_TEST(retval);
    return (retval);
}

/*
 * symlink -- make a symbolic link
#% symlink	dvp	L U U
#% symlink	vpp	- U -
#
# XXX - note that the return vnode has already been VRELE'ed
#	by the filesystem layer.  To use it you must use vget,
#	possibly with a further namei.
#
 vop_symlink {
     IN WILLRELE struct vnode *dvp;
     OUT WILLRELE struct vnode **vpp;
     IN struct componentname *cnp;
     IN struct vattr *vap;
     IN char *target;

     We are responsible for freeing the namei buffer, 
	 it is done in hfs_makenode().

*/

int
hfs_symlink(ap)
    struct vop_symlink_args /* {
        struct vnode *a_dvp;
        struct vnode **a_vpp;
        struct componentname *a_cnp;
        struct vattr *a_vap;
        char *a_target;
    } */ *ap;
{
    register struct vnode *vp, **vpp = ap->a_vpp;
	struct proc *p = current_proc();
	struct hfsnode *hp;
	u_int32_t dfltClump;
    int len, retval;
    DBG_FUNC_NAME("symlink");
    DBG_VOP_LOCKS_DECL(2);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_LOCKS_INIT(0,ap->a_dvp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
    DBG_VOP_LOCKS_INIT(1,*ap->a_vpp, VOPDBG_IGNORE, VOPDBG_UNLOCKED, VOPDBG_IGNORE, VOPDBG_POS);

    if (VTOVCB(ap->a_dvp)->vcbSigWord != kHFSPlusSigWord) {
    	VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
        vput(ap->a_dvp);
        DBG_VOP((" ...sorry HFS disks don't support symbolic links.\n"));
        DBG_VOP_LOCKS_TEST(EOPNOTSUPP);
        return (EOPNOTSUPP);
    }

	/* Create the vnode */
	retval = hfs_makenode(IFLNK | ap->a_vap->va_mode, 0, ap->a_dvp,
				vpp, ap->a_cnp, p);
    DBG_VOP_UPDATE_VP(1, *ap->a_vpp);

    if (retval != E_NONE) {
        DBG_VOP_LOCKS_TEST(retval);
        return (retval);
	}


    vp = *vpp;
    len = strlen(ap->a_target);
	hp = VTOH(vp);
	dfltClump = hp->fcbClmpSize;
	/* make clump size minimal */
	hp->fcbClmpSize = VTOVCB(vp)->blockSize;
    retval = vn_rdwr(UIO_WRITE, vp, ap->a_target, len, (off_t)0,
                     UIO_SYSSPACE, IO_NODELOCKED, ap->a_cnp->cn_cred, (int *)0,
                     (struct proc *)0);
	hp->fcbClmpSize = dfltClump;


    vput(vp);
    DBG_VOP_LOCKS_TEST(retval);
    return (retval);
}


/*
 * Dummy dirents to simulate the "." and ".." entries of the directory
 * in a hfs filesystem.  HFS doesn't provide these on disk.  Note that
 * the size of these entries is the smallest needed to represent them
 * (only 12 byte each).
 */
static hfsdotentry  rootdots[2] = {
	{
		1,				/* d_fileno */
		sizeof(struct hfsdotentry),	/* d_reclen */
		DT_DIR,				/* d_type */
		1,				/* d_namlen */
		"."				/* d_name */
    },
    {
		1,				/* d_fileno */
		sizeof(struct hfsdotentry),	/* d_reclen */
		DT_DIR,				/* d_type */
		2,				/* d_namlen */
		".."				/* d_name */
	}
};

static hfsdotentry  emptyentry = { 0 };

/*	4.3 Note:
*	There is some confusion as to what the semantics of uio_offset are.
*	In ufs, it represents the actual byte offset within the directory
*	"file."  HFS, however, just uses it as an entry counter - essentially
*	assuming that it has no meaning except to the hfs_readdir function.
*	This approach would be more efficient here, but some callers may
*	assume the uio_offset acts like a byte offset.  NFS in fact
*	monkeys around with the offset field a lot between readdir calls.
*
*	The use of the resid uiop->uio_resid and uiop->uio_iov->iov_len
*	fields is a mess as well.  The libc function readdir() returns
*	NULL (indicating the end of a directory) when either
*	the getdirentries() syscall (which calls this and returns
*	the size of the buffer passed in less the value of uiop->uio_resid)
*	returns 0, or a direct record with a d_reclen of zero.
*	nfs_server.c:rfs_readdir(), on the other hand, checks for the end
*	of the directory by testing uiop->uio_resid == 0.  The solution
*	is to pad the size of the last struct direct in a given
*	block to fill the block if we are not at the end of the directory.
*/

struct callbackstate {
	u_int32_t	cbs_parentID;
	u_int32_t	cbs_hiddenDirID;
	off_t		cbs_lastoffset;
	struct uio *	cbs_uio;
	ExtendedVCB *	cbs_vcb;
	int16_t		cbs_hfsPlus;
	int16_t		cbs_result;
};


SInt32
ProcessCatalogEntry(const CatalogKey *ckp, const CatalogRecord *crp,
		    u_int16_t recordLen, struct callbackstate *state)
{
	CatalogName *cnp;
	size_t utf8chars;
	u_int32_t curID;
	OSErr result;
	struct dirent catent;
	
	if (state->cbs_hfsPlus)
		curID = ckp->hfsPlus.parentID;
	else
		curID = ckp->hfs.parentID;

	/* We're done when parent directory changes */
	if (state->cbs_parentID != curID) {
lastitem:
/*
 * The NSDirectoryList class chokes on empty records (it doesnt check d_reclen!)
 * so remove padding for now...
 */
#if 0
		/*
		 * Pad the end of list with an empty record.
		 * This eliminates an extra call by readdir(3c).
		 */
		catent.d_fileno = 0;
		catent.d_reclen = 0;
		catent.d_type = 0;
		catent.d_namlen = 0;
		*(int32_t*)&catent.d_name[0] = 0;

		state->cbs_lastoffset = state->cbs_uio->uio_offset;

		state->cbs_result = uiomove((caddr_t) &catent, 12, state->cbs_uio);
		if (state->cbs_result == 0)
			state->cbs_result = ENOENT;
#else
		state->cbs_lastoffset = state->cbs_uio->uio_offset;
		state->cbs_result = ENOENT;
#endif
		return (0);	/* stop */
	}

	if (state->cbs_hfsPlus) {
		switch(crp->recordType) {
		case kHFSPlusFolderRecord:
			catent.d_type = DT_DIR;
			catent.d_fileno = crp->hfsPlusFolder.folderID;
			break;
		case kHFSPlusFileRecord:
			catent.d_type = DT_REG;
			catent.d_fileno = crp->hfsPlusFile.fileID;
			break;
		default:
			return (0);	/* stop */
		};

		cnp = (CatalogName*) &ckp->hfsPlus.nodeName;
		result = utf8_encodestr(cnp->ustr.unicode, cnp->ustr.length * sizeof(UniChar),
				catent.d_name, &utf8chars, kdirentMaxNameBytes + 1, ':', 0);
		if (result == ENAMETOOLONG) {
			result = ConvertUnicodeToUTF8Mangled(cnp->ustr.length * sizeof(UniChar),
			    	cnp->ustr.unicode, kdirentMaxNameBytes + 1, (ByteCount*)&utf8chars, catent.d_name, catent.d_fileno);		
		}
	} else { /* hfs */
		switch(crp->recordType) {
		case kHFSFolderRecord:
			catent.d_type = DT_DIR;
			catent.d_fileno = crp->hfsFolder.folderID;
			break;
		case kHFSFileRecord:
			catent.d_type = DT_REG;
			catent.d_fileno = crp->hfsFile.fileID;
			break;
		default:
			return (0);	/* stop */
		};

		cnp = (CatalogName*) ckp->hfs.nodeName;
		result = hfs_to_utf8(state->cbs_vcb, cnp->pstr, kdirentMaxNameBytes + 1,
				    (ByteCount *)&utf8chars, catent.d_name);
		/*
		 * When an HFS name cannot be encoded with the current
		 * volume encoding we use MacRoman as a fallback.
		 */
		if (result)
			result = mac_roman_to_utf8(cnp->pstr, kdirentMaxNameBytes + 1,
				    (ByteCount *)&utf8chars, catent.d_name);
	}

	catent.d_namlen = utf8chars;
	catent.d_reclen = DIRENTRY_SIZE(utf8chars);
	
	/* hide our private meta data directory */
	if (curID == kRootDirID				&&
	    catent.d_fileno == state->cbs_hiddenDirID	&&
	    catent.d_type == DT_DIR)
		goto lastitem;

	state->cbs_lastoffset = state->cbs_uio->uio_offset;

	/* if this entry won't fit then we're done */
	if (catent.d_reclen > state->cbs_uio->uio_resid)
		return (0);	/* stop */

	state->cbs_result = uiomove((caddr_t) &catent, catent.d_reclen, state->cbs_uio);

	/* continue iteration if there's room */
	return (state->cbs_result == 0  &&
		state->cbs_uio->uio_resid >= AVERAGE_HFSDIRENTRY_SIZE);
}

/*
 * NOTE: We require a minimal buffer size of DIRBLKSIZ for two reasons. One, it is the same value
 * returned be stat() call as the block size. This is mentioned in the man page for getdirentries():
 * "Nbytes must be greater than or equal to the block size associated with the file,
 * see stat(2)". Might as well settle on the same size of ufs. Second, this makes sure there is enough
 * room for the . and .. entries that have to added manually.
 */

/* 			
#% readdir	vp	L L L
#
vop_readdir {
    IN struct vnode *vp;
    INOUT struct uio *uio;
    IN struct ucred *cred;
    INOUT int *eofflag;
    OUT int *ncookies;
    INOUT u_long **cookies;
    */
static int
hfs_readdir(ap)
struct vop_readdir_args /* {
    struct vnode *vp;
    struct uio *uio;
    struct ucred *cred;
    int *eofflag;
    int *ncookies;
    u_long **cookies;
} */ *ap;
{
    register struct uio *uio = ap->a_uio;
    struct hfsnode 		*hp = VTOH(ap->a_vp);
    struct proc			*p = current_proc();
    ExtendedVCB 		*vcb = HTOVCB(hp);
    off_t 				off = uio->uio_offset;
	u_int32_t dirID = H_FILEID(hp);
	int retval = 0;
    OSErr				result = noErr;
	u_int32_t diroffset;
	BTreeIterator bi;
	CatalogIterator *cip;
	u_int16_t op;
	struct callbackstate state;
	int eofflag = 0;

    DBG_FUNC_NAME("readdir");
    DBG_VOP_LOCKS_DECL(1);

    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_POS);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));
    DBG_HFS_NODE_CHECK(ap->a_vp);

    /* We assume it's all one big buffer... */
	if (uio->uio_iovcnt > 1 || uio->uio_resid < AVERAGE_HFSDIRENTRY_SIZE) {
		return EINVAL;
	};

	/* Create the entries for . and .. */
	if (uio->uio_offset < sizeof(rootdots)) {
		caddr_t dep;
		size_t dotsize;
		
		rootdots[0].d_fileno = dirID;
		rootdots[1].d_fileno = H_DIRID(hp);

		if (uio->uio_offset == 0) {
			dep = (caddr_t) &rootdots[0];
			dotsize = 2* sizeof(struct hfsdotentry);
		} else if (uio->uio_offset == sizeof(struct hfsdotentry)) {
			dep = (caddr_t) &rootdots[1];
			dotsize = sizeof(struct hfsdotentry);
		} else {
			retval = EINVAL;
			goto Exit;
		}

		retval = uiomove(dep, dotsize, uio);
		if (retval != 0)
			goto Exit;
	}
	
	diroffset = uio->uio_offset;

	/* lock catalog b-tree */
	retval = hfs_metafilelocking(VTOHFS(ap->a_vp), kHFSCatalogFileID, LK_SHARED, p);
	if (retval != E_NONE)
		goto Exit;

	/* get an iterator and position it */
	cip = GetCatalogIterator(vcb, dirID, diroffset);

	result = PositionIterator(cip, diroffset, &bi, &op);
	if (result == cmNotFound) {
		eofflag = 1;
		retval = 0;
		AgeCatalogIterator(cip);
		goto cleanup;
	} else if ((retval = MacToVFSError(result)))
		goto cleanup;

	state.cbs_hiddenDirID = VCBTOHFS(vcb)->hfs_private_metadata_dir;
	state.cbs_lastoffset = cip->currentOffset;
	state.cbs_vcb = vcb;
	state.cbs_uio = uio;
	state.cbs_result = 0;
	state.cbs_parentID = dirID;

	if (vcb->vcbSigWord == kHFSPlusSigWord)
		state.cbs_hfsPlus = 1;
	else
		state.cbs_hfsPlus = 0;

	/* process as many entries as possible... */
	result = BTIterateRecords(GetFileControlBlock(vcb->catalogRefNum), op, &bi,
		 (IterateCallBackProcPtr)ProcessCatalogEntry, &state);

	if (state.cbs_result)
		retval = state.cbs_result;
	else
		retval = MacToVFSError(result);

	if (retval == ENOENT) {
		eofflag = 1;
		retval = 0;
	}

	if (retval == 0) {
		cip->currentOffset = state.cbs_lastoffset;
		cip->nextOffset = uio->uio_offset;
		UpdateCatalogIterator(&bi, cip);
	}

cleanup:
	if (retval) {
		cip->volume = 0;
		cip->folderID = 0;
		AgeCatalogIterator(cip);
	}

	(void) ReleaseCatalogIterator(cip);

	/* unlock catalog b-tree */
	(void) hfs_metafilelocking(VTOHFS(ap->a_vp), kHFSCatalogFileID, LK_RELEASE, p);

    if (retval != E_NONE) {
        DBG_ERR(("%s: retval %d when trying to read directory %ld: %s\n",funcname, retval,
                H_FILEID(hp), H_NAME(hp)));
		goto Exit;

	}
	
	/* were we already past eof ? */
	if (uio->uio_offset == off) {
		retval = E_NONE;
		goto Exit;
	}
	
	if (vcb->vcbSigWord == kHFSPlusSigWord)
		hp->h_nodeflags |= IN_ACCESS;

    /* Bake any cookies */
    if (!retval && ap->a_ncookies != NULL) {
        struct dirent* dpStart;
        struct dirent* dpEnd;
        struct dirent* dp;
        int ncookies;
        u_long *cookies;
        u_long *cookiep;

        /*
        * Only the NFS server uses cookies, and it loads the
        * directory block into system space, so we can just look at
        * it directly.
        */
	    if (uio->uio_segflg != UIO_SYSSPACE)
            panic("hfs_readdir: unexpected uio from NFS server");
        dpStart = (struct dirent *)(uio->uio_iov->iov_base - (uio->uio_offset - off));
        dpEnd = (struct dirent *) uio->uio_iov->iov_base;
        for (dp = dpStart, ncookies = 0;
            dp < dpEnd && dp->d_reclen != 0;
            dp = (struct dirent *)((caddr_t)dp + dp->d_reclen))
            ncookies++;
        MALLOC(cookies, u_long *, ncookies * sizeof(u_long), M_TEMP, M_WAITOK);
        for (dp = dpStart, cookiep = cookies;
            dp < dpEnd;
            dp = (struct dirent *)((caddr_t) dp + dp->d_reclen)) {
            off += dp->d_reclen;
            *cookiep++ = (u_long) off;
        }
        *ap->a_ncookies = ncookies;
        *ap->a_cookies = cookies;
    }

Exit:;

    if (ap->a_eofflag)
	    *ap->a_eofflag = eofflag;

    DBG_VOP_LOCKS_TEST(retval);
    return (retval);
}


/*
 * readdirattr operation will return attributes for the items in the
 * directory specified. 
 *
 * It does not do . and .. entries. The problem is if you are at the root of the
 * hfs directory and go to .. you could be crossing a mountpoint into a
 * different (ufs) file system. The attributes that apply for it may not 
 * apply for the file system you are doing the readdirattr on. To make life 
 * simpler, this call will only return entries in its directory, hfs like.
     * TO DO LATER: 
     * 1.getattrlist creates a thread record if the objpermanentid attribute
     *  is requested. Just do EINVAL for now and fix later. 
     * 2. more than one for uiovcnt support.
     * 3. put knohint (hints) in state for next call in
     * 4. credentials checking when rest of hfs does it.
     * 5. Do return permissions concatenation ???
 */

/* 			
#
#% readdirattr	vp	L L L
#
vop_readdirattr {
	IN struct vnode *vp;
	IN struct attrlist *alist;
	INOUT struct uio *uio;
	IN u_long maxcount:
	IN u_long options;
	OUT u_long *newstate;
	OUT int *eofflag;
	OUT u_long *actualCount;
	OUT u_long **cookies;
	IN struct ucred *cred;
};
*/
static int
hfs_readdirattr(ap)
struct vop_readdirattr_args /* {
    struct vnode *vp;
    struct attrlist *alist;
    struct uio *uio;
    u_long maxcount:
    u_long options;
    int *newstate;
    int *eofflag;
    u_long *actualcount;
    u_long **cookies;
    struct ucred *cred;
} */ *ap;
{
    struct vnode 	*vp = ap->a_vp;
    struct attrlist 	*alist = ap->a_alist;
    register struct 	uio *uio = ap->a_uio;
    u_long 		maxcount = ap->a_maxcount;
    u_long 		ncookies;
    ExtendedVCB 	*vcb = HTOVCB(VTOH(vp));
    UInt32		dirID =  H_FILEID(VTOH(vp));
    struct proc		*proc = current_proc(); /* could get this out of uio */
    off_t		startoffset = uio->uio_offset;
    struct hfsCatalogInfo catInfo;
    UInt32		index;
    int			retval = 0;
    u_long 		fixedblocksize;
    u_long 		maxattrblocksize;
    u_long		currattrbufsize;
    void 		*attrbufptr = NULL;
    void 		*attrptr;
    void 		*varptr;
    struct vnode *entryvnode;
 

    *(ap->a_actualcount) = 0;
    *(ap->a_eofflag) = 0;

    /* check for invalid options, check vnode, and buffer space */
    if (((ap->a_options & ~FSOPT_NOINMEMUPDATE) != 0) ||
    	(vp == NULL) || 
		(uio->uio_resid <= 0) || (uio->uio_iovcnt > 1))
	   return EINVAL;

    /* this call doesn't take volume attributes */
    if ((alist->bitmapcount != ATTR_BIT_MAP_COUNT) ||
        ((alist->commonattr & ~ATTR_CMN_VALIDMASK) != 0) ||
        (alist->volattr  != 0) ||
        ((alist->dirattr & ~ATTR_DIR_VALIDMASK) != 0) ||
        ((alist->fileattr & ~ATTR_FILE_VALIDMASK) != 0) ||
        ((alist->forkattr & ~ATTR_FORK_VALIDMASK) != 0)) 
        return EINVAL;

    /* Reject requests for unsupported options for now: */
    if ((alist->commonattr & (ATTR_CMN_NAMEDATTRCOUNT | ATTR_CMN_NAMEDATTRLIST)) ||
        (alist->fileattr & (ATTR_FILE_FILETYPE | ATTR_FILE_FORKCOUNT | ATTR_FILE_FORKLIST)) ||
	(alist->commonattr & ATTR_CMN_OBJPERMANENTID) ) 
        return EINVAL;

    /* getattrlist and searchfs use a secondary buffer to malloc and then use
     * uiomove afterwards. It's an extra copy, but for now leave it alone
    */
    fixedblocksize = (sizeof(u_long) + AttributeBlockSize(alist)); /* u_long for length */
    maxattrblocksize = fixedblocksize;
    if (alist->commonattr & ATTR_CMN_NAME) 
	maxattrblocksize += kHFSPlusMaxFileNameBytes + 1;
    MALLOC(attrbufptr, void *, maxattrblocksize, M_TEMP, M_WAITOK);
    attrptr = attrbufptr;
    varptr = (char *)attrbufptr + fixedblocksize;  /* Point to variable-length storage */

    /* Since attributes passed back can contain variable ones (name), we can't just use 
     * uio_offset as is. We thus force it to represent fixed size of hfsdirentries
     * as hfs_readdir was originally doing. If this all we need to represent the current
     * state, then ap->a_state is not needed at all.
    */
    /* index = ap->a_state;  should not be less than 1 */
    index = (uio->uio_offset / sizeof(struct dirent)) + 1;
	INIT_CATALOGDATA(&catInfo.nodeData, 0);

    /* Lock catalog b-tree */  
    if ((retval = hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_SHARED, proc)) != E_NONE)
        goto exit;

    /* HFS Catalog does not have a bulk directory enumeration call. Do it one at
     * time, using hints. GetCatalogOffspring takes care of hfsplus and name issues
     * for us, so that's a win. Later, implement GetCatalogOffspringBulk.
    */
    catInfo.hint = kNoHint; /* note, we may want to save the latest in state */
    while ((uio->uio_resid >= 0) && (maxcount !=0 )) {
        /* better to check uio_resid against max or fixedblocksize, but won't work.
         * Depending on if dir or file, the attributes returned will be different.
         * Thus fixedblocksize is too large in some cases.Also, the variable
         * part (like name)  could be between fixedblocksize and the max.
        */
        OSErr result = GetCatalogOffspring(vcb, dirID, index, &catInfo.nodeData, NULL, NULL);
        if (result != noErr) {
            if (result == cmNotFound) {
                *(ap->a_eofflag) = TRUE;
                retval = E_NONE;
            }
            else retval = MacToVFSError(result);
            break;
        }

	/* hide our private meta data directory as does hfs_readdir */
	if ((dirID == kRootDirID)  &&
	    catInfo.nodeData.cnd_nodeID == VCBTOHFS(vcb)->hfs_private_metadata_dir  &&
	    catInfo.nodeData.cnd_type == kCatalogFolderNode) {

	    ++index;
	     continue;
	}

        *((u_long *)attrptr)++ = 0; /* move it past length */

       	if (ap->a_options & FSOPT_NOINMEMUPDATE) {
	  /* vp okay to use instead of root vp */
	  PackCatalogInfoAttributeBlock(alist, vp, &catInfo, &attrptr, &varptr);
	} else {
	  /* Check to see if there's a vnode for this item in the cache: */
	  entryvnode = hfs_vhashget(H_DEV(VTOH(vp)), catInfo.nodeData.cnd_nodeID, kDefault);
	  if (entryvnode != NULL) {
	    PackAttributeBlock(alist, entryvnode, &catInfo, &attrptr, &varptr);
	    vput(entryvnode);
	  } else {
	    /* vp okay to use instead of root vp */
	    PackCatalogInfoAttributeBlock(alist, vp, &catInfo, &attrptr, &varptr);
	  };
	};
	currattrbufsize = *((u_long *)attrbufptr) = ((char *)varptr - (char *)attrbufptr);
		
        /* now check if we can't fit in the buffer space remaining */
        if (currattrbufsize > uio->uio_resid) 
            break;
		else { 
	            retval = uiomove((caddr_t)attrbufptr, currattrbufsize, ap->a_uio);
	            if (retval != E_NONE)
	                break; 
	            attrptr = attrbufptr;
                    varptr = (char *)attrbufptr + fixedblocksize;  /* Point to variable-length storage */
	            index++;
		    *ap->a_actualcount += 1;
		    maxcount--;
		}
    };
    *ap->a_newstate = VTOH(vp)->h_meta->h_mtime;/* before we unlock, know the mod date */
   /* Unlock catalog b-tree, finally. Ties up the everything during enumeration */
    (void) hfs_metafilelocking( VTOHFS(ap->a_vp), kHFSCatalogFileID, LK_RELEASE, proc );
    
	CLEAN_CATALOGDATA(&catInfo.nodeData);

    if (!retval && ap->a_cookies != NULL) { /* CHECK THAT 0 wasn't passed in */
        void* dpStart;
        void* dpEnd;
        void* dp;
	u_long *cookies;
	u_long *cookiep;

        /* Only the NFS server uses cookies, and it loads the
         * directory block into system space, so we can just look at
         * it directly.
        */
        if (uio->uio_segflg != UIO_SYSSPACE) /* || uio->uio_iovcnt != 1 checked earlier */
            panic("hfs_readdirattr: unexpected uio from NFS server");
        dpStart = uio->uio_iov->iov_base - (uio->uio_offset - startoffset);
        dpEnd = uio->uio_iov->iov_base;
        MALLOC(cookies, u_long *, (*ap->a_actualcount)*sizeof(u_long), M_TEMP, M_WAITOK);
        for (dp = dpStart, cookiep = cookies;
            dp < dpEnd;
             dp = ((caddr_t) dp + *((u_long *)dp))) {
        	*cookiep++ = (u_long)((caddr_t)dp + sizeof(u_long));
        }
	*ap->a_cookies = cookies;
    }
    
   uio->uio_offset = startoffset + (*ap->a_actualcount)*sizeof(struct dirent);

exit:
    if (attrbufptr != NULL)
	FREE(attrbufptr, M_TEMP);
    return (retval);
}


/*
 * Return target name of a symbolic link
#% readlink	vp	L L L
#
 vop_readlink {
     IN struct vnode *vp;
     INOUT struct uio *uio;
     IN struct ucred *cred;
     */

int
hfs_readlink(ap)
struct vop_readlink_args /* {
struct vnode *a_vp;
struct uio *a_uio;
struct ucred *a_cred;
} */ *ap;
{
    int retval;
    DBG_FUNC_NAME("readlink");
    DBG_VOP_LOCKS_DECL(1);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));

    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_POS);
    retval = VOP_READ(ap->a_vp, ap->a_uio, 0, ap->a_cred);
    /* clear IN_ACCESS to prevent needless update of symlink vnode */
    VTOH(ap->a_vp)->h_nodeflags &= ~IN_ACCESS;

    DBG_VOP_LOCKS_TEST(retval);
    return (retval);

}


/*
 * hfs abort op, called after namei() when a CREATE/DELETE isn't actually
 * done. If a buffer has been saved in anticipation of a CREATE, delete it.
#% abortop	dvp	= = =
#
 vop_abortop {
     IN struct vnode *dvp;
     IN struct componentname *cnp;

     */

/* ARGSUSED */

static int
hfs_abortop(ap)
struct vop_abortop_args /* {
    struct vnode *a_dvp;
    struct componentname *a_cnp;
} */ *ap;
{
    DBG_FUNC_NAME("abortop");
    DBG_VOP_LOCKS_DECL(1);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_PRINT_VNODE_INFO(ap->a_dvp);
    DBG_VOP_PRINT_CPN_INFO(ap->a_cnp);DBG_VOP_CONT(("\n"));


    DBG_VOP_LOCKS_INIT(0,ap->a_dvp, VOPDBG_IGNORE, VOPDBG_IGNORE, VOPDBG_IGNORE, VOPDBG_POS);

    if ((ap->a_cnp->cn_flags & (HASBUF | SAVESTART)) == HASBUF) {
        FREE_ZONE(ap->a_cnp->cn_pnbuf, ap->a_cnp->cn_pnlen, M_NAMEI);
    }
    DBG_VOP_LOCKS_TEST(E_NONE);
    return (E_NONE);
}

// int	prthfsactive = 0;		/* 1 => print out reclaim of active vnodes */

/*
#% inactive	vp	L U U
#
 vop_inactive {
     IN struct vnode *vp;
     IN struct proc *p;

*/

static int
hfs_inactive(ap)
struct vop_inactive_args /* {
    struct vnode *a_vp;
} */ *ap;
{
	struct vnode *vp = ap->a_vp;
	struct hfsnode *hp = VTOH(vp);
	struct proc *p = ap->a_p;
	struct timeval tv;
	int error = 0;
	extern int prtactive;

	DBG_FUNC_NAME("inactive");
	DBG_VOP_LOCKS_DECL(1);
	DBG_VOP_PRINT_FUNCNAME();
	DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));

	DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_ZERO);


	if (prtactive && vp->v_usecount <= 0)
		vprint("hfs_inactive: pushing active", vp);

	if (vp->v_usecount != 0)
		DBG_VOP(("%s: bad usecount = %d\n",funcname,vp->v_usecount ));

	/*
	 * Ignore nodes related to stale file handles.
	 */
	if (hp->h_meta->h_mode == 0)
		goto out;
	
	/*
	 * Check for a postponed deletion
	 */
	if (hp->h_meta->h_metaflags & IN_DELETED) {			
		hp->h_meta->h_metaflags &= ~IN_DELETED;

		error = vinvalbuf(vp, 0, NOCRED, p, 0, 0);
		if (error) goto out;
        
        if(UBCINFOEXISTS(vp))
                (void)ubc_setsize(vp, (off_t)0);

		/* Lock both trees
		 * Note: we do not need a lock on the private metadata directory
		 * since it never has a vnode associated with it.
		 */
        error = hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_EXCLUSIVE | LK_CANRECURSE, p);
        if (error) goto out;
        error = hfs_metafilelocking(VTOHFS(vp), kHFSExtentsFileID, LK_EXCLUSIVE | LK_CANRECURSE, p);
        if (error) {
                (void) hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_RELEASE, p);
                goto out;
        }

        if (hp->h_meta->h_metaflags & IN_DATANODE) {
                char iNodeName[32];

                MAKE_INODE_NAME(iNodeName, hp->h_meta->h_indnodeno);
                error = hfsDelete(HTOVCB(hp), VTOHFS(vp)->hfs_private_metadata_dir, iNodeName, TRUE, H_HINT(hp));
        } else {
                /* XXX can we leave orphaned sibling? */
                error = hfsDelete(HTOVCB(hp), H_DIRID(hp), H_NAME(hp), TRUE, H_HINT(hp));
                if (error == ENOENT) {
                        /* try by fileID as a backup */
                        error = hfsDelete(HTOVCB(hp), H_FILEID(hp), NULL, TRUE, H_HINT(hp));
                }
        }

        (void) hfs_metafilelocking(VTOHFS(vp), kHFSExtentsFileID, LK_RELEASE, p);
        (void) hfs_metafilelocking(VTOHFS(vp), kHFSCatalogFileID, LK_RELEASE, p);
        if (error) goto out;

        hp->h_meta->h_metaflags |=  IN_NOEXISTS;
        hp->h_meta->h_mode = 0;
		/* clear the block mappings */
		hp->fcbPLen = (u_int64_t)0;
		bzero(&hp->fcbExtents, sizeof(HFSPlusExtentRecord));

        hp->h_nodeflags &= ~(IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE);
        }

        if (hp->h_nodeflags & (IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE)) {
                tv = time;
                VOP_UPDATE(vp, &tv, &tv, 0);
        }

out:
	VOP_UNLOCK(vp, 0, p);
	/*
	 * If we are done with the inode, reclaim it
	 * so that it can be reused immediately.
	 */
	if (hp->h_meta->h_mode == 0)
		vrecycle(vp, (struct slock *)0, p);
	
	/* XXX SER Here we might want to get rid of any other forks
	 * The problem is that if we call vrecycle(), our structure
	 * disappear from under us, we would need to remember, and expect
	 * things to go to null or to disappear
	 * But it stillw would be a good thing to remove vnodes
	 * referencing stale data
		 */

	DBG_VOP_LOCKS_TEST(E_NONE);
	return (E_NONE);
}

/*
 Ignored since the locks are gone......
#% reclaim	vp	U I I
#
 vop_reclaim {
	 IN struct vnode *vp;
	 IN struct proc *p;

	 */

static int
hfs_reclaim(ap)
struct vop_reclaim_args /* {
	struct vnode *a_vp;
} */ *ap;
{
	struct vnode 	*vp = ap->a_vp;
	struct hfsnode 	*hp = VTOH(vp);
	void			*tdata = vp->v_data;
	char			*tname;
	Boolean			freeMeta = true;
	struct vnode 	*devvp = NULL;
	
	extern int prtactive;
	DBG_FUNC_NAME("reclaim");
	DBG_VOP_LOCKS_DECL(1);
	DBG_VOP_PRINT_FUNCNAME();
	DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));

	DBG_VOP_LOCKS_INIT(0, ap->a_vp, VOPDBG_UNLOCKED, VOPDBG_IGNORE, VOPDBG_IGNORE, VOPDBG_ZERO);

	/*
		NOTE: XXX vnodes need careful handling because fork vnodes that failed to be
			  created in their entirity could be getting cleaned up here.
	 */

	if (prtactive && vp->v_usecount != 0)
		vprint("hfs_reclaim(): pushing active", vp);

	hp->h_nodeflags |= IN_ALLOCATING;	/* Mark this as being incomplete */
	/*
	 * This will remove the entry from the hash AND the sibling list
	 * This will make sure everything is in a stable state to see if we can remove the meta
	 * i.e. if this is the only fork...the sibling list will be empty
	 */
	hfs_vhashrem(hp);	

	DBG_ASSERT(tdata != NULL);
	DBG_ASSERT(hp->h_meta != NULL);
   
   	devvp = hp->h_meta->h_devvp;		/* For later releasing */
	hp->h_meta->h_usecount--;   	

	/* release the file meta if this is the last fork */
    if (H_FORKTYPE(hp)==kDataFork || H_FORKTYPE(hp)==kRsrcFork) {
    	if (hp->h_meta->h_siblinghead.cqh_first != (void *) &hp->h_meta->h_siblinghead)
			freeMeta = false;
	};

    if (freeMeta) {
		DBG_ASSERT(hp->h_meta->h_usecount == 0);
		if (hp->h_meta->h_metaflags & IN_LONGNAME) {
			tname = H_NAME(hp);
			DBG_ASSERT(tname != NULL);
			FREE(tname, M_TEMP);
		  	}
		  FREE_ZONE(hp->h_meta, sizeof(struct hfsfilemeta), M_HFSFMETA);
		  hp->h_meta = NULL;
	    }
	else
		DBG_ASSERT(hp->h_meta->h_usecount == 1);
   

	/*
	 * Purge old data structures associated with the inode.
	 */
	cache_purge(vp);
	if (devvp) {
		vrele(devvp);
	};

	/* Free our data structs */
	FREE_ZONE(tdata, sizeof(struct hfsnode), M_HFSNODE);
	vp->v_data = NULL;

	DBG_VOP_LOCKS_TEST(E_NONE);
	return (E_NONE);
}


/*
 * Lock an hfsnode. If its already locked, set the WANT bit and sleep.
#% lock		vp	U L U
#
 vop_lock {
     IN struct vnode *vp;
     IN int flags;
     IN struct proc *p;
     */

static int
hfs_lock(ap)
struct vop_lock_args /* {
    struct vnode *a_vp;
    int a_flags;
    struct proc *a_p;
} */ *ap;
{
	struct vnode * vp = ap->a_vp;
	struct hfsnode *hp = VTOH(ap->a_vp);
	int			retval;

	DBG_FUNC_NAME("lock");
	DBG_VOP_LOCKS_DECL(1);
	DBG_VOP_PRINT_FUNCNAME();DBG_VOP_CONT((" "));
	DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT((" flags = 0x%08X.\n", ap->a_flags));
	DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_UNLOCKED, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_ZERO);

	retval = lockmgr(&hp->h_lock, ap->a_flags, &vp->v_interlock, ap->a_p);
	if (retval != E_NONE) {
		if ((ap->a_flags & LK_NOWAIT) == 0)
			DBG_ERR(("hfs_lock: error %d trying to lock vnode (flags = 0x%08X).\n", retval, ap->a_flags));
		goto Err_Exit;
	};

Err_Exit:;
	DBG_ASSERT(*((int*)&vp->v_interlock) == 0);
	DBG_VOP_LOCKS_TEST(retval);
	return (retval);
}

/*
 * Unlock an hfsnode.
#% unlock	vp	L U L
#
 vop_unlock {
     IN struct vnode *vp;
     IN int flags;
     IN struct proc *p;

     */
int
hfs_unlock(ap)
struct vop_unlock_args /* {
    struct vnode *a_vp;
    int a_flags;
    struct proc *a_p;
} */ *ap;
{
	struct hfsnode *hp = VTOH(ap->a_vp);
	struct vnode *vp = ap->a_vp;
	int		retval = E_NONE;

	DBG_FUNC_NAME("unlock");
	DBG_VOP_LOCKS_DECL(1);
	DBG_VOP_PRINT_FUNCNAME();
	DBG_VOP_PRINT_VNODE_INFO(vp);DBG_VOP_CONT((" flags = 0x%08X.\n", ap->a_flags));
	DBG_VOP_LOCKS_INIT(0,vp, VOPDBG_LOCKED, VOPDBG_UNLOCKED, VOPDBG_LOCKED, VOPDBG_ZERO);


	DBG_ASSERT((ap->a_flags & (LK_EXCLUSIVE|LK_SHARED)) == 0);
	retval = lockmgr(&hp->h_lock, ap->a_flags | LK_RELEASE, &vp->v_interlock, ap->a_p);
	if (retval != E_NONE) {
		DEBUG_BREAK_MSG(("hfs_unlock: error %d trying to unlock vnode (forktype = %d).\n", retval, H_FORKTYPE(hp)));
	};

	DBG_ASSERT(*((int*)&vp->v_interlock) == 0);
	DBG_VOP_LOCKS_TEST(retval);
	return (retval);
}


/*
 * Print out the contents of an hfsnode.
#% print	vp	= = =
#
 vop_print {
     IN struct vnode *vp;
     */
int
hfs_print(ap)
struct vop_print_args /* {
    struct vnode *a_vp;
} */ *ap;
{
	register struct vnode * vp = ap->a_vp;
	register struct hfsnode *hp = VTOH( vp);
	DBG_FUNC_NAME("print");
	DBG_VOP_LOCKS_DECL(1);
	DBG_VOP_PRINT_FUNCNAME();
	DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);

	DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_IGNORE, VOPDBG_IGNORE, VOPDBG_IGNORE, VOPDBG_POS);

	printf("tag VT_HFS, dirID %d, on dev %d, %d", H_DIRID(hp),
		   major(H_DEV(hp)), minor(H_DEV(hp)));
	/* lockmgr_printinfo(&hp->h_lock); */
	printf("\n");
	DBG_VOP_LOCKS_TEST(E_NONE);
	return (E_NONE);
}


/*
 * Check for a locked hfsnode.
#% islocked	vp	= = =
#
 vop_islocked {
     IN struct vnode *vp;

     */
int
hfs_islocked(ap)
struct vop_islocked_args /* {
    struct vnode *a_vp;
} */ *ap;
{
    int		lockStatus;
    //DBG_FUNC_NAME("islocked");
    //DBG_VOP_LOCKS_DECL(1);
    //DBG_VOP_PRINT_FUNCNAME();
    //DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);

    //DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_IGNORE, VOPDBG_IGNORE, VOPDBG_IGNORE, VOPDBG_ZERO);

    lockStatus = lockstatus(&VTOH( ap->a_vp)->h_lock);
    //DBG_VOP_LOCKS_TEST(E_NONE);
    return (lockStatus);
}

/*

#% pathconf	vp	L L L
#
 vop_pathconf {
     IN struct vnode *vp;
     IN int name;
     OUT register_t *retval;

     */
static int
hfs_pathconf(ap)
struct vop_pathconf_args /* {
    struct vnode *a_vp;
    int a_name;
    int *a_retval;
} */ *ap;
{
    int retval = E_NONE;
    DBG_FUNC_NAME("pathconf");
    DBG_VOP_LOCKS_DECL(1);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);

    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_POS);

    DBG_HFS_NODE_CHECK (ap->a_vp);

    switch (ap->a_name) {
        case _PC_LINK_MAX:
#if HFS_HARDLINKS
	if (VTOVCB(ap->a_vp)->vcbSigWord == kHFSPlusSigWord)
		*ap->a_retval = HFS_LINK_MAX;
	else
		*ap->a_retval = 1;
#else
	*ap->a_retval = 1;
#endif
            break;
        case _PC_NAME_MAX:
            *ap->a_retval = kHFSPlusMaxFileNameBytes;	/* max # of characters x max utf8 representation */
            break;
        case _PC_PATH_MAX:
            *ap->a_retval = PATH_MAX; /* 1024 */
            break;
        case _PC_CHOWN_RESTRICTED:
            *ap->a_retval = 1;
            break;
        case _PC_NO_TRUNC:
            *ap->a_retval = 0;
            break;
        default:
            retval = EINVAL;
    }

    DBG_VOP_LOCKS_TEST(retval);
    return (retval);
}





/*
 * Advisory record locking support
#% advlock	vp	U U U
#
 vop_advlock {
     IN struct vnode *vp;
     IN caddr_t id;
     IN int op;
     IN struct flock *fl;
     IN int flags;

     */
int
hfs_advlock(ap)
struct vop_advlock_args /* {
    struct vnode *a_vp;
    caddr_t  a_id;
    int  a_op;
    struct flock *a_fl;
    int  a_flags;
} */ *ap;
{
    register struct hfsnode *hp = VTOH(ap->a_vp);
    register struct flock *fl = ap->a_fl;
    register struct hfslockf *lock;
    off_t start, end;
    int retval;
    DBG_FUNC_NAME("advlock");
    DBG_VOP_LOCKS_DECL(1);
    DBG_VOP_PRINT_FUNCNAME();
    DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP(("\n"));
    DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_UNLOCKED, VOPDBG_POS);
    /*
     * Avoid the common case of unlocking when inode has no locks.
     */
    if (hp->h_lockf == (struct hfslockf *)0) {
        if (ap->a_op != F_SETLK) {
            fl->l_type = F_UNLCK;
            return (0);
        }
    }
    /*
     * Convert the flock structure into a start and end.
     */
    start = 0;
    switch (fl->l_whence) {
        case SEEK_SET:
        case SEEK_CUR:
            /*
             * Caller is responsible for adding any necessary offset
             * when SEEK_CUR is used.
             */
            start = fl->l_start;
            break;

        case SEEK_END:
            start = HTOFCB(hp)->fcbEOF + fl->l_start;
            break;

        default:
            return (EINVAL);
    }

    if (start < 0)
        return (EINVAL);
    if (fl->l_len == 0)
        end = -1;
    else
        end = start + fl->l_len - 1;

    /*
     * Create the hfslockf structure
     */
    MALLOC(lock, struct hfslockf *, sizeof *lock, M_LOCKF, M_WAITOK);
    lock->lf_start = start;
    lock->lf_end = end;
    lock->lf_id = ap->a_id;
    lock->lf_hfsnode = hp;
    lock->lf_type = fl->l_type;
    lock->lf_next = (struct hfslockf *)0;
    TAILQ_INIT(&lock->lf_blkhd);
    lock->lf_flags = ap->a_flags;
    /*
     * Do the requested operation.
     */
    switch(ap->a_op) {
        case F_SETLK:
            retval = hfs_setlock(lock);
            break;

        case F_UNLCK:
            retval = hfs_clearlock(lock);
            FREE(lock, M_LOCKF);
            break;

        case F_GETLK:
            retval = hfs_getlock(lock, fl);
            FREE(lock, M_LOCKF);
            break;

        default:
            retval = EINVAL;
            _FREE(lock, M_LOCKF);
            break;
    }

    DBG_VOP_LOCKS_TEST(retval);
    return (retval);
}



/*
 * Update the access, modified, and node change times as specified by the
 * IACCESS, IUPDATE, and ICHANGE flags respectively. The IMODIFIED flag is
 * used to specify that the node needs to be updated but that the times have
 * already been set. The access and modified times are taken from the second
 * and third parameters; the node change time is always taken from the current
 * time. If waitfor is set, then wait for the disk write of the node to
 * complete.
 */
/*
#% update	vp	L L L
	IN struct vnode *vp;
	IN struct timeval *access;
	IN struct timeval *modify;
	IN int waitfor;
*/

int
hfs_update(ap)
	struct vop_update_args /* {
		struct vnode *a_vp;
		struct timeval *a_access;
		struct timeval *a_modify;
		int a_waitfor;
	} */ *ap;
{
	struct hfsnode	*hp;
	struct proc		*p;
	hfsCatalogInfo 	catInfo;
	char 			*filename;
	char iNodeName[32];
	u_int32_t 		pid;
	int				retval;
	ExtendedVCB *vcb;
	DBG_FUNC_NAME("update");
	DBG_VOP_LOCKS_DECL(1);
	DBG_VOP_PRINT_FUNCNAME();
	DBG_VOP_PRINT_VNODE_INFO(ap->a_vp);DBG_VOP_CONT(("\n"));
	DBG_VOP_LOCKS_INIT(0,ap->a_vp, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_LOCKED, VOPDBG_ZERO);

	hp = VTOH(ap->a_vp);

	DBG_ASSERT(hp && hp->h_meta);
	DBG_ASSERT(*((int*)&ap->a_vp->v_interlock) == 0);

	if ((H_FORKTYPE(hp) == kSysFile) ||
	    (VTOVFS(ap->a_vp)->mnt_flag & MNT_RDONLY) ||
	    (hp->h_meta->h_mode == 0)) {
		hp->h_nodeflags &= ~(IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE);
		DBG_VOP_LOCKS_TEST(0);
		return (0);
	}

    if (H_FORKTYPE(hp) == kSysFile) {
        hp->h_nodeflags &= ~(IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE);
        DBG_VOP_LOCKS_TEST(0);
        return (0);
    }

    if (VTOVFS(ap->a_vp)->mnt_flag & MNT_RDONLY) {
        hp->h_nodeflags &= ~(IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE);
        DBG_VOP_LOCKS_TEST(0);
        return (0);
    }
	
    /* Check to see if MacOS set the fcb to be dirty, if so, translate it to IN_MODIFIED */
    if (HTOFCB(hp)->fcbFlags &fcbModifiedMask)
        hp->h_nodeflags |= IN_MODIFIED;

    if ((hp->h_nodeflags & (IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE)) == 0) {
        DBG_VOP_LOCKS_TEST(0);
        return (0);
    };

	if (hp->h_nodeflags & IN_ACCESS)
		hp->h_meta->h_atime = ap->a_access->tv_sec;
	if (hp->h_nodeflags & IN_UPDATE)
		hp->h_meta->h_mtime = ap->a_modify->tv_sec;
	if (hp->h_nodeflags & IN_CHANGE) {
		hp->h_meta->h_ctime = time.tv_sec;
		/*
		 * HFS dates that WE set must be adjusted for DST
		 */
		if ((HTOVCB(hp)->vcbSigWord == kHFSSigWord) && gTimeZone.tz_dsttime) {
			hp->h_meta->h_ctime += 3600;
			hp->h_meta->h_mtime = hp->h_meta->h_ctime;
		}
	}

	p = current_proc();
	filename = H_NAME(hp);
	pid = H_DIRID(hp);
	vcb = HTOVCB(hp);
	catInfo.hint = H_HINT(hp);

#if HFS_HARDLINKS
	/*
	 * Force an update of the indirect node instead of the link
	 * by using the name and parent of the indirect node.
	 */
	if (hp->h_meta->h_metaflags & IN_DATANODE) {
		MAKE_INODE_NAME(iNodeName, hp->h_meta->h_indnodeno);
		filename = iNodeName;
		pid = VCBTOHFS(vcb)->hfs_private_metadata_dir;
	}
#endif

	INIT_CATALOGDATA(&catInfo.nodeData, kCatNameNoCopyName);

	/*
	 * Since VOP_UPDATE can be called from withing another VOP (eg VOP_RENAME),
	 * the Catalog b-tree may aready be locked by the current thread. So we
	 * allow recursive locking of the Catalog from within VOP_UPDATE.
	 */
	/* Lock the Catalog b-tree file */
	retval = hfs_metafilelocking(HTOHFS(hp), kHFSCatalogFileID, LK_EXCLUSIVE | LK_CANRECURSE, p);
	if (retval) {
        DBG_VOP_LOCKS_TEST(retval);
        return (retval);
    };

	retval = hfs_getcatalog(vcb, pid, filename, -1, &catInfo);
	if (retval != noErr) {
		(void) hfs_metafilelocking(HTOHFS(hp), kHFSCatalogFileID, LK_RELEASE, p);
		retval = MacToVFSError(retval);
		goto Err_Exit;
	};

	H_HINT(hp) = catInfo.hint;
	CopyVNodeToCatalogNode (HTOV(hp), &catInfo.nodeData);

	retval = UpdateCatalogNode(vcb, pid, filename, H_HINT(hp), &catInfo.nodeData);

	 /* unlock the Catalog b-tree file */
	(void) hfs_metafilelocking(HTOHFS(hp), kHFSCatalogFileID, LK_RELEASE, p);

    if (retval != noErr) {				/* from UpdateCatalogNode() */
        retval = MacToVFSError(retval);
        goto Err_Exit;
    };

	/* After the updates are finished, clear the flags */
	hp->h_nodeflags &= ~(IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE);
	HTOFCB(hp)->fcbFlags &= ~fcbModifiedMask;

	/* Update general data */
    if (ap->a_vp->v_type == VDIR) {
    		hp->h_meta->h_nlink = 2 + catInfo.nodeData.cnd_valence;
		hp->h_meta->h_size = sizeof(rootdots) + 
			(catInfo.nodeData.cnd_valence * AVERAGE_HFSDIRENTRY_SIZE);
		if (hp->h_meta->h_size < MAX_HFSDIRENTRY_SIZE)
			hp->h_meta->h_size < MAX_HFSDIRENTRY_SIZE;
	} else {
		hp->h_meta->h_size = (off_t)vcb->blockSize *
		    (off_t)(catInfo.nodeData.cnd_rsrcfork.totalBlocks +
		     catInfo.nodeData.cnd_datafork.totalBlocks);
	}


Err_Exit:    

	CLEAN_CATALOGDATA(&catInfo.nodeData);

    DBG_VOP_LOCKS_TEST(retval);
	return (retval);
}


/*
 * Initialize the vnode associated with a new hfsnode, 
 * handle aliased vnodes.
 */
int
hfs_vinit(mntp, specops, fifoops, vpp)
	struct mount *mntp;
	int (**specops)(void *);
	int (**fifoops)(void *);
	struct vnode **vpp;
{
	struct hfsnode *hp;
	struct vnode *vp, *nvp;

	vp = *vpp;
	hp = VTOH(vp);
	/* vp->v_type set in CopyCatalogToHFSNode */ 
	switch(vp->v_type) {
	case VCHR:
	case VBLK:
		vp->v_op = specops;
		if ((nvp = checkalias(vp, hp->h_meta->h_rdev, mntp))) {
			/*
			 * Discard unneeded vnode, but save its hfsnode.
			 * Note that the lock is carried over in the hfsnode
			 * to the replacement vnode.
			 */
			nvp->v_data = vp->v_data;
			vp->v_data = NULL;
			vp->v_op = spec_vnodeop_p;
			vrele(vp);
			vgone(vp);
			/*
			 * Reinitialize aliased hfsnode.
			 */

			hp->h_vp = nvp;				
			vp = nvp;
		}
		break;
	case VFIFO:
#if FIFO
		vp->v_op = fifoops;
		break;
#else
		return (EOPNOTSUPP);
#endif
	default:
		break;
	}
	if (H_FILEID(hp) == kRootDirID)
                vp->v_flag |= VROOT;

	*vpp = vp;
	return (0);
}

/*
 * Allocate a new node
 *
 * Upon leaving, namei buffer must be freed.
 *
 */
static int
hfs_makenode(mode, rawdev, dvp, vpp, cnp, p)
    int mode;
    dev_t rawdev;
    struct vnode *dvp;
    struct vnode **vpp;
    struct componentname *cnp;
	struct proc *p;
{
    register struct hfsnode *hp, *parhp;
    struct timeval 			tv;
    struct vnode 			*tvp;
    struct hfsCatalogInfo 	catInfo;
    ExtendedVCB				*vcb;
    UInt8					forkType;
    int 					retval;
	int hasmetalock = 0;
    DBG_FUNC_NAME("makenode");

    parhp	= VTOH(dvp);
    vcb		= HTOVCB(parhp);
    *vpp	= NULL;
	tvp 	= NULL;
    if ((mode & IFMT) == 0)
        mode |= IFREG;

#if HFS_DIAGNOSTIC
    if ((cnp->cn_flags & HASBUF) == 0)
        panic("hfs_makenode: no name");
#endif

	/* lock catalog b-tree */
	retval = hfs_metafilelocking(VTOHFS(dvp),
				kHFSCatalogFileID, LK_EXCLUSIVE, p);
	if (retval != E_NONE)
		goto bad1;
	else
		hasmetalock = 1;

    /* Create the Catalog B*-Tree entry */
    retval = hfsCreate(vcb, H_FILEID(parhp), cnp->cn_nameptr, mode);
    if (retval != E_NONE) {
        DBG_ERR(("%s: hfsCreate FAILED: %s, %s\n", funcname, cnp->cn_nameptr, H_NAME(parhp)));
        goto bad1;
    }

    /* Look up the catalog entry just created: */
	INIT_CATALOGDATA(&catInfo.nodeData, 0);
	catInfo.hint = kNoHint;

    retval = hfs_getcatalog(vcb, H_FILEID(parhp), cnp->cn_nameptr, cnp->cn_namelen, &catInfo);
    if (retval != E_NONE) {
        DBG_ERR(("%s: hfs_getcatalog FAILED: %s, %s\n", funcname, cnp->cn_nameptr, H_NAME(parhp)));
        goto bad1;
    }

	/* unlock catalog b-tree */
	hasmetalock = 0;
	(void) hfs_metafilelocking(VTOHFS(dvp),
			kHFSCatalogFileID, LK_RELEASE, p);

	/* hfs plus has additional metadata to initialize */
	if (vcb->vcbSigWord == kHFSPlusSigWord) {
		u_int32_t pflags;
		int catmode;
		
		if (VTOVFS(dvp)->mnt_flag & MNT_UNKNOWNPERMISSIONS) {
			catInfo.nodeData.cnd_ownerID = VTOHFS(dvp)->hfs_uid;
			catInfo.nodeData.cnd_groupID = VTOHFS(dvp)->hfs_gid;
			catmode = mode;
		} else {
			catInfo.nodeData.cnd_ownerID = cnp->cn_cred->cr_uid;
			catInfo.nodeData.cnd_groupID = parhp->h_meta->h_gid;
			catmode = mode;
		}

		switch (catmode & IFMT) {
		case IFLNK:
			catInfo.nodeData.cnd_ownerID = parhp->h_meta->h_uid;
			break;

		case IFCHR:
		case IFBLK:
			/* XXX should we move this to post hfsGet? */
			catInfo.nodeData.cnd_rawDevice = rawdev;
			/*
			 * Don't tag as a special file (BLK or CHR) until *after*
			 * hfsGet is called.  This insures that the checkalias call
			 * is defered until hfs_mknod completes.
			 */
			catmode = (catmode & ~IFMT) | IFREG;
			break;
		}

		if ((catmode & ISGID) && !groupmember(parhp->h_meta->h_gid, cnp->cn_cred) &&
			suser(cnp->cn_cred, NULL))
			catmode &= ~ISGID;

		if (cnp->cn_flags & ISWHITEOUT)
			pflags = UF_OPAQUE;
		else
			pflags = 0;

		/*
		 * The 32-bit pflags field has two bytes of significance which
		 * are stored separately as admin and owner flags.
		 *
		 *		+------------------------------------+
		 * pflags:	|XXXXXXXX|   SF    |XXXXXXXX|   UF   |
		 *		+------------------------------------+
		 */
		catInfo.nodeData.cnd_adminFlags = (pflags >> 16) & 0x00FF;
		catInfo.nodeData.cnd_ownerFlags = pflags & 0x00FF;
		catInfo.nodeData.cnd_mode = catmode;
	}

    /* Create a vnode for the object just created: */
    forkType = (catInfo.nodeData.cnd_type == kCatalogFolderNode) ? kDirectory : kDataFork;
    retval = hfs_vcreate(vcb, &catInfo, forkType, &tvp);

	CLEAN_CATALOGDATA(&catInfo.nodeData);		/* Should do nothing */

    if (retval)  goto bad1;		/* from hfs_vcreate() */

	/* flush out pflags, mode, gid, uid and rdev */
    tv = time;
    if (vcb->vcbSigWord == kHFSPlusSigWord) {
        hp = VTOH(tvp);
		/* reset mode and v_type in case it was BLK/CHR */
		hp->h_meta->h_mode = mode;
		tvp->v_type = IFTOVT(mode);
        hp->h_meta->h_metaflags &= ~IN_UNSETACCESS;
        hp->h_nodeflags |= IN_ACCESS | IN_CHANGE | IN_UPDATE;
        if ((retval = VOP_UPDATE(tvp, &tv, &tv, 1)))
            goto bad2;
    }

	VTOH(dvp)->h_nodeflags |= IN_CHANGE | IN_UPDATE;
    if ((retval = VOP_UPDATE(dvp, &tv, &tv, 1)))
        goto bad2;

    if ((cnp->cn_flags & (HASBUF | SAVESTART)) == HASBUF) {
        FREE_ZONE(cnp->cn_pnbuf, cnp->cn_pnlen, M_NAMEI);
    };
    vput(dvp);
    if (UBCINFOMISSING(tvp) || UBCINFORECLAIMED(tvp))
        ubc_info_init(tvp);

    *vpp = tvp;
    return (0);

bad2:
    /*
     * Write retval occurred trying to update the node
     * or the directory so must deallocate the node.
    */
    /* XXX SER In the future maybe set *vpp to 0xdeadbeef for testing */
    vput(tvp);

bad1:
	if (hasmetalock) {
		/* unlock catalog b-tree */
		hasmetalock = 0;
		(void) hfs_metafilelocking(VTOHFS(dvp),
				kHFSCatalogFileID, LK_RELEASE, p);
	}
    if ((cnp->cn_flags & (HASBUF | SAVESTART)) == HASBUF) {
        FREE_ZONE(cnp->cn_pnbuf, cnp->cn_pnlen, M_NAMEI);
    };
    vput(dvp);

    return (retval);
}


#if DBG_VOP_TEST_LOCKS

/* XXX SER Add passing in the flags...might not be a serious error if locked */

void DbgVopTest( int maxSlots,
                 int retval,
                 VopDbgStoreRec *VopDbgStore,
                 char *funcname)
{
    int index;

    for (index = 0; index < maxSlots; index++)
      {
        if (VopDbgStore[index].id != index) {
            DEBUG_BREAK_MSG(("%s: DBG_VOP_LOCK: invalid id field (%d) in target entry (#%d).\n", funcname, VopDbgStore[index].id, index));
        };

        if ((VopDbgStore[index].vp != NULL) &&
            ((VopDbgStore[index].vp->v_data==NULL) || (VTOH(VopDbgStore[index].vp)->h_valid != HFS_VNODE_MAGIC)))
            continue;

        if (VopDbgStore[index].vp != NULL)
            debug_check_vnode(VopDbgStore[index].vp, 0);

        switch (VopDbgStore[index].inState)
          {
            case VOPDBG_IGNORE:
            case VOPDBG_SAME:
                /* Do Nothing !!! */
                break;
            case VOPDBG_LOCKED:
            case VOPDBG_UNLOCKED:
            case VOPDBG_LOCKNOTNIL:
              {
                  if (VopDbgStore[index].vp == NULL && (VopDbgStore[index].inState != VOPDBG_LOCKNOTNIL)) {
                      DBG_ERR (("%s: InState check: Null vnode ptr in entry #%d\n", funcname, index));
                  } else if (VopDbgStore[index].vp != NULL) {
                      switch (VopDbgStore[index].inState)
                        {
                          case VOPDBG_LOCKED:
                          case VOPDBG_LOCKNOTNIL:
                              if (VopDbgStore[index].inValue == 0)
                                {
                                  DBG_ERR (("%s: Entry: not LOCKED:", funcname));
                                  DBG_VOP_PRINT_VNODE_INFO(VopDbgStore[index].vp); 
                                  DBG_ERR (("\n"));
                                }
                              break;
                          case VOPDBG_UNLOCKED:
                              if (VopDbgStore[index].inValue != 0)
                                {
                                  DBG_ERR (("%s: Entry: not UNLOCKED:", funcname));
                                  DBG_VOP_PRINT_VNODE_INFO(VopDbgStore[index].vp);
                                  DBG_ERR (("\n"));
                                }
                              break;
                        }
                  }
                  break;
              }
            default:
                DBG_ERR (("%s: DBG_VOP_LOCK on entry: bad lock test value: %d\n", funcname, VopDbgStore[index].errState));
          }


        if (retval != 0)
          {
            switch (VopDbgStore[index].errState)
              {
                case VOPDBG_IGNORE:
                    /* Do Nothing !!! */
                    break;
                case VOPDBG_LOCKED:
                case VOPDBG_UNLOCKED:
                case VOPDBG_SAME:
                  {
                      if (VopDbgStore[index].vp == NULL) {
                          DBG_ERR (("%s: ErrState check: Null vnode ptr in entry #%d\n", funcname, index));
                      } else {
                          VopDbgStore[index].outValue = lockstatus(&VTOH(VopDbgStore[index].vp)->h_lock);
                          switch (VopDbgStore[index].errState)
                            {
                              case VOPDBG_LOCKED:
                                  if (VopDbgStore[index].outValue == 0)
                                    {
                                      DBG_ERR (("%s: Error: not LOCKED:", funcname));
                                      DBG_VOP_PRINT_VNODE_INFO(VopDbgStore[index].vp); 
                                      DBG_ERR(("\n"));
                                   }
                                  break;
                              case VOPDBG_UNLOCKED:
                                  if (VopDbgStore[index].outValue != 0)
                                    {
                                      DBG_ERR (("%s: Error: not UNLOCKED:", funcname));
                                      DBG_VOP_PRINT_VNODE_INFO(VopDbgStore[index].vp); 
                                      DBG_ERR(("\n"));
                                    }
                                  break;
                              case VOPDBG_SAME:
                                  if (VopDbgStore[index].outValue != VopDbgStore[index].inValue)
                                      DBG_ERR (("%s: Error: In/Out locks are DIFFERENT: 0x%x, inis %d and out is %d\n", funcname, (u_int)VopDbgStore[index].vp, VopDbgStore[index].inValue, VopDbgStore[index].outValue));
                                  break;
                            }
                      }
                      break;
                  }
                case VOPDBG_LOCKNOTNIL:
                    if (VopDbgStore[index].vp != NULL) {
                        VopDbgStore[index].outValue = lockstatus(&VTOH(VopDbgStore[index].vp)->h_lock);
                        if (VopDbgStore[index].outValue == 0)
                            DBG_ERR (("%s: Error: Not LOCKED: 0x%x\n", funcname, (u_int)VopDbgStore[index].vp));
                    }
                    break;
                default:
                    DBG_ERR (("%s: Error: bad lock test value: %d\n", funcname, VopDbgStore[index].errState));
              }
          }
        else
          {
            switch (VopDbgStore[index].outState)
              {
                case VOPDBG_IGNORE:
                    /* Do Nothing !!! */
                    break;
                case VOPDBG_LOCKED:
                case VOPDBG_UNLOCKED:
                case VOPDBG_SAME:
                    if (VopDbgStore[index].vp == NULL) {
                        DBG_ERR (("%s: OutState: Null vnode ptr in entry #%d\n", funcname, index));
                    };
                    if (VopDbgStore[index].vp != NULL)
                      {
                        VopDbgStore[index].outValue = lockstatus(&VTOH(VopDbgStore[index].vp)->h_lock);
                        switch (VopDbgStore[index].outState)
                          {
                            case VOPDBG_LOCKED:
                                if (VopDbgStore[index].outValue == 0)
                                  {
                                    DBG_ERR (("%s: Out: not LOCKED:", funcname));
                                    DBG_VOP_PRINT_VNODE_INFO(VopDbgStore[index].vp); 
                                    DBG_ERR (("\n"));
                                 }
                                break;
                            case VOPDBG_UNLOCKED:
                                if (VopDbgStore[index].outValue != 0)
                                  {
                                    DBG_ERR (("%s: Out: not UNLOCKED:", funcname));
                                    DBG_VOP_PRINT_VNODE_INFO(VopDbgStore[index].vp); 
                                    DBG_ERR (("\n"));
                                  }
                                break;
                            case VOPDBG_SAME:
                                if (VopDbgStore[index].outValue != VopDbgStore[index].inValue)
                                    DBG_ERR (("%s: Out: In/Out locks are DIFFERENT: 0x%x, in is %d and out is %d\n", funcname, (u_int)VopDbgStore[index].vp, VopDbgStore[index].inValue, VopDbgStore[index].outValue));
                                break;
                          }
                      }
                    break;
                case VOPDBG_LOCKNOTNIL:
                    if (VopDbgStore[index].vp != NULL) {
                        if (&VTOH(VopDbgStore[index].vp)->h_lock == NULL) {
                            DBG_ERR (("%s: DBG_VOP_LOCK on out: Null lock on vnode 0x%x\n", funcname, (u_int)VopDbgStore[index].vp));
                        }
                        else {
                            VopDbgStore[index].outValue = lockstatus(&VTOH(VopDbgStore[index].vp)->h_lock);
                            if (VopDbgStore[index].outValue == 0)
                              {
                                DBG_ERR (("%s: DBG_VOP_LOCK on out: Should be LOCKED:", funcname));
                                DBG_VOP_PRINT_VNODE_INFO(VopDbgStore[index].vp); DBG_ERR (("\n"));
                              }
                        }
                    }
                    break;
                default:
                    DBG_ERR (("%s: DBG_VOP_LOCK on out: bad lock test value: %d\n", funcname, VopDbgStore[index].outState));
              }
          }

        VopDbgStore[index].id = -1;		/* Invalidate the entry to allow panic-free re-use */
      }	
}

#endif /* DBG_VOP_TEST_LOCKS */

/*
 * Wrapper for special device reads
 */
int
hfsspec_read(ap)
	struct vop_read_args /* {
		struct vnode *a_vp;
		struct uio *a_uio;
		int  a_ioflag;
		struct ucred *a_cred;
	} */ *ap;
{

	/*
	 * Set access flag.
	 */
	VTOH(ap->a_vp)->h_nodeflags |= IN_ACCESS;
	return (VOCALL (spec_vnodeop_p, VOFFSET(vop_read), ap));
}

/*
 * Wrapper for special device writes
 */
int
hfsspec_write(ap)
	struct vop_write_args /* {
		struct vnode *a_vp;
		struct uio *a_uio;
		int  a_ioflag;
		struct ucred *a_cred;
	} */ *ap;
{

	/*
	 * Set update and change flags.
	 */
	VTOH(ap->a_vp)->h_nodeflags |= IN_CHANGE | IN_UPDATE;
	return (VOCALL (spec_vnodeop_p, VOFFSET(vop_write), ap));
}

/*
 * Wrapper for special device close
 *
 * Update the times on the hfsnode then do device close.
 */
int
hfsspec_close(ap)
	struct vop_close_args /* {
		struct vnode *a_vp;
		int  a_fflag;
		struct ucred *a_cred;
		struct proc *a_p;
	} */ *ap;
{
	struct vnode *vp = ap->a_vp;
	struct hfsnode *hp = VTOH(vp);

	simple_lock(&vp->v_interlock);
	if (ap->a_vp->v_usecount > 1)
		HFSTIMES(hp, &time, &time);
	simple_unlock(&vp->v_interlock);
	return (VOCALL (spec_vnodeop_p, VOFFSET(vop_close), ap));
}

#if FIFO
/*
 * Wrapper for fifo reads
 */
int
hfsfifo_read(ap)
	struct vop_read_args /* {
		struct vnode *a_vp;
		struct uio *a_uio;
		int  a_ioflag;
		struct ucred *a_cred;
	} */ *ap;
{
	extern int (**fifo_vnodeop_p)(void *);

	/*
	 * Set access flag.
	 */
	VTOH(ap->a_vp)->h_nodeflags |= IN_ACCESS;
	return (VOCALL (fifo_vnodeop_p, VOFFSET(vop_read), ap));
}

/*
 * Wrapper for fifo writes
 */
int
hfsfifo_write(ap)
	struct vop_write_args /* {
		struct vnode *a_vp;
		struct uio *a_uio;
		int  a_ioflag;
		struct ucred *a_cred;
	} */ *ap;
{
	extern int (**fifo_vnodeop_p)(void *);

	/*
	 * Set update and change flags.
	 */
	VTOH(ap->a_vp)->h_nodeflags |= IN_CHANGE | IN_UPDATE;
	return (VOCALL (fifo_vnodeop_p, VOFFSET(vop_write), ap));
}

/*
 * Wrapper for fifo close
 *
 * Update the times on the hfsnode then do device close.
 */
int
hfsfifo_close(ap)
	struct vop_close_args /* {
		struct vnode *a_vp;
		int  a_fflag;
		struct ucred *a_cred;
		struct proc *a_p;
	} */ *ap;
{
	extern int (**fifo_vnodeop_p)(void *);
	struct vnode *vp = ap->a_vp;
	struct hfsnode *hp = VTOH(vp);

	simple_lock(&vp->v_interlock);
	if (ap->a_vp->v_usecount > 1)
		HFSTIMES(hp, &time, &time);
	simple_unlock(&vp->v_interlock);
	return (VOCALL (fifo_vnodeop_p, VOFFSET(vop_close), ap));
}
#endif /* FIFO */


/*****************************************************************************
*
*	VOP Tables
*
*****************************************************************************/

#define VOPFUNC int (*)(void *)

struct vnodeopv_entry_desc hfs_vnodeop_entries[] = {
    { &vop_default_desc, (VOPFUNC)vn_default_error },
    { &vop_lookup_desc, (VOPFUNC)hfs_cache_lookup },		/* lookup */
    { &vop_create_desc, (VOPFUNC)hfs_create },			/* create */
    { &vop_mknod_desc, (VOPFUNC)hfs_mknod },			/* mknod */
    { &vop_open_desc, (VOPFUNC)hfs_open },			/* open */
    { &vop_close_desc, (VOPFUNC)hfs_close },			/* close */
    { &vop_access_desc, (VOPFUNC)hfs_access },			/* access */
    { &vop_getattr_desc, (VOPFUNC)hfs_getattr },		/* getattr */
    { &vop_setattr_desc, (VOPFUNC)hfs_setattr },		/* setattr */
    { &vop_read_desc, (VOPFUNC)hfs_read },			/* read */
    { &vop_write_desc, (VOPFUNC)hfs_write },			/* write */
    { &vop_ioctl_desc, (VOPFUNC)hfs_ioctl },			/* ioctl */
    { &vop_select_desc, (VOPFUNC)hfs_select },			/* select */
    { &vop_exchange_desc, (VOPFUNC)hfs_exchange },		/* exchange */
    { &vop_mmap_desc, (VOPFUNC)hfs_mmap },			/* mmap */
    { &vop_fsync_desc, (VOPFUNC)hfs_fsync },			/* fsync */
    { &vop_seek_desc, (VOPFUNC)hfs_seek },			/* seek */
    { &vop_remove_desc, (VOPFUNC)hfs_remove },			/* remove */
#if HFS_HARDLINKS
    { &vop_link_desc, (VOPFUNC)hfs_link },			/* link */
#else
    { &vop_link_desc, (VOPFUNC)err_link },	/* link (NOT SUPPORTED) */
#endif
    { &vop_rename_desc, (VOPFUNC)hfs_rename },			/* rename */
    { &vop_mkdir_desc, (VOPFUNC)hfs_mkdir },			/* mkdir */
    { &vop_rmdir_desc, (VOPFUNC)hfs_rmdir },			/* rmdir */
    { &vop_mkcomplex_desc, (VOPFUNC)hfs_mkcomplex },		/* mkcomplex */
    { &vop_getattrlist_desc, (VOPFUNC)hfs_getattrlist },  /* getattrlist */
    { &vop_setattrlist_desc, (VOPFUNC)hfs_setattrlist },  /* setattrlist */
    { &vop_symlink_desc, (VOPFUNC)hfs_symlink },		/* symlink */
    { &vop_readdir_desc, (VOPFUNC)hfs_readdir },		/* readdir */
    { &vop_readdirattr_desc, (VOPFUNC)hfs_readdirattr },  /* readdirattr */
    { &vop_readlink_desc, (VOPFUNC)hfs_readlink },		/* readlink */
    { &vop_abortop_desc, (VOPFUNC)hfs_abortop },		/* abortop */
    { &vop_inactive_desc, (VOPFUNC)hfs_inactive },		/* inactive */
    { &vop_reclaim_desc, (VOPFUNC)hfs_reclaim },		/* reclaim */
    { &vop_lock_desc, (VOPFUNC)hfs_lock },			/* lock */
    { &vop_unlock_desc, (VOPFUNC)hfs_unlock },			/* unlock */
    { &vop_bmap_desc, (VOPFUNC)hfs_bmap },			/* bmap */
    { &vop_strategy_desc, (VOPFUNC)hfs_strategy },		/* strategy */
    { &vop_print_desc, (VOPFUNC)hfs_print },			/* print */
    { &vop_islocked_desc, (VOPFUNC)hfs_islocked },		/* islocked */
    { &vop_pathconf_desc, (VOPFUNC)hfs_pathconf },		/* pathconf */
    { &vop_advlock_desc, (VOPFUNC)hfs_advlock },		/* advlock */
    { &vop_reallocblks_desc, (VOPFUNC)hfs_reallocblks },  /* reallocblks */
    { &vop_truncate_desc, (VOPFUNC)hfs_truncate },		/* truncate */
    { &vop_allocate_desc, (VOPFUNC)hfs_allocate },		/* allocate */
    { &vop_update_desc, (VOPFUNC)hfs_update },			/* update */
    { &vop_searchfs_desc, (VOPFUNC)hfs_search },		/* search fs */
    { &vop_bwrite_desc, (VOPFUNC)hfs_bwrite },			/* bwrite */
    { &vop_pagein_desc, (VOPFUNC)hfs_pagein },			/* pagein */
    { &vop_pageout_desc,(VOPFUNC) hfs_pageout },		/* pageout */
    { &vop_copyfile_desc, (VOPFUNC)err_copyfile },		/* copyfile */
    { &vop_blktooff_desc, (VOPFUNC)hfs_blktooff },		/* blktooff */
    { &vop_offtoblk_desc, (VOPFUNC)hfs_offtoblk },		/* offtoblk */
    { &vop_cmap_desc, (VOPFUNC)hfs_cmap },			/* cmap */
    { NULL, (VOPFUNC)NULL }
};

struct vnodeopv_desc hfs_vnodeop_opv_desc =
{ &hfs_vnodeop_p, hfs_vnodeop_entries };

int (**hfs_specop_p)(void *);
struct vnodeopv_entry_desc hfs_specop_entries[] = {
	{ &vop_default_desc, (VOPFUNC)vn_default_error },
	{ &vop_lookup_desc, (VOPFUNC)spec_lookup },		/* lookup */
	{ &vop_create_desc, (VOPFUNC)spec_create },		/* create */
	{ &vop_mknod_desc, (VOPFUNC)spec_mknod },		/* mknod */
	{ &vop_open_desc, (VOPFUNC)spec_open },			/* open */
	{ &vop_close_desc, (VOPFUNC)hfsspec_close },		/* close */
	{ &vop_access_desc, (VOPFUNC)hfs_access },		/* access */
	{ &vop_getattr_desc, (VOPFUNC)hfs_getattr },		/* getattr */
	{ &vop_setattr_desc, (VOPFUNC)hfs_setattr },		/* setattr */
	{ &vop_read_desc, (VOPFUNC)hfsspec_read },		/* read */
	{ &vop_write_desc, (VOPFUNC)hfsspec_write },		/* write */
	{ &vop_lease_desc, (VOPFUNC)spec_lease_check },		/* lease */
	{ &vop_ioctl_desc, (VOPFUNC)spec_ioctl },		/* ioctl */
	{ &vop_select_desc, (VOPFUNC)spec_select },		/* select */
	{ &vop_revoke_desc, (VOPFUNC)spec_revoke },		/* revoke */
	{ &vop_mmap_desc, (VOPFUNC)spec_mmap },			/* mmap */
	{ &vop_fsync_desc, (VOPFUNC)hfs_fsync },		/* fsync */
	{ &vop_seek_desc, (VOPFUNC)spec_seek },			/* seek */
	{ &vop_remove_desc, (VOPFUNC)spec_remove },		/* remove */
	{ &vop_link_desc, (VOPFUNC)spec_link },			/* link */
	{ &vop_rename_desc, (VOPFUNC)spec_rename },		/* rename */
	{ &vop_mkdir_desc, (VOPFUNC)spec_mkdir },		/* mkdir */
	{ &vop_rmdir_desc, (VOPFUNC)spec_rmdir },		/* rmdir */
	{ &vop_symlink_desc, (VOPFUNC)spec_symlink },		/* symlink */
	{ &vop_readdir_desc, (VOPFUNC)spec_readdir },		/* readdir */
	{ &vop_readlink_desc, (VOPFUNC)spec_readlink },		/* readlink */
	{ &vop_abortop_desc, (VOPFUNC)spec_abortop },		/* abortop */
	{ &vop_inactive_desc, (VOPFUNC)hfs_inactive },		/* inactive */
	{ &vop_reclaim_desc, (VOPFUNC)hfs_reclaim },		/* reclaim */
	{ &vop_lock_desc, (VOPFUNC)hfs_lock },			/* lock */
	{ &vop_unlock_desc, (VOPFUNC)hfs_unlock },		/* unlock */
	{ &vop_bmap_desc, (VOPFUNC)spec_bmap },			/* bmap */
	{ &vop_strategy_desc, (VOPFUNC)spec_strategy },		/* strategy */
	{ &vop_print_desc, (VOPFUNC)hfs_print },		/* print */
	{ &vop_islocked_desc, (VOPFUNC)hfs_islocked },		/* islocked */
	{ &vop_pathconf_desc, (VOPFUNC)spec_pathconf },		/* pathconf */
	{ &vop_advlock_desc, (VOPFUNC)spec_advlock },		/* advlock */
	{ &vop_blkatoff_desc, (VOPFUNC)spec_blkatoff },		/* blkatoff */
	{ &vop_valloc_desc, (VOPFUNC)spec_valloc },		/* valloc */
	{ &vop_reallocblks_desc, (VOPFUNC)spec_reallocblks },	/* reallocblks */
	{ &vop_vfree_desc, (VOPFUNC)err_vfree },		/* vfree */
	{ &vop_truncate_desc, (VOPFUNC)spec_truncate },		/* truncate */
	{ &vop_update_desc, (VOPFUNC)hfs_update },		/* update */
	{ &vop_bwrite_desc, (VOPFUNC)hfs_bwrite },
	{ &vop_devblocksize_desc, (VOPFUNC)spec_devblocksize }, /* devblocksize */
	{ &vop_pagein_desc, (VOPFUNC)hfs_pagein },		/* Pagein */
	{ &vop_pageout_desc, (VOPFUNC)hfs_pageout },		/* Pageout */
        { &vop_copyfile_desc, (VOPFUNC)err_copyfile },		/* copyfile */
	{ &vop_blktooff_desc, (VOPFUNC)hfs_blktooff },		/* blktooff */
	{ &vop_offtoblk_desc, (VOPFUNC)hfs_offtoblk },		/* offtoblk */
	{ (struct vnodeop_desc*)NULL, (VOPFUNC)NULL }
};
struct vnodeopv_desc hfs_specop_opv_desc =
	{ &hfs_specop_p, hfs_specop_entries };

#if FIFO
int (**hfs_fifoop_p)(void *);
struct vnodeopv_entry_desc hfs_fifoop_entries[] = {
	{ &vop_default_desc, (VOPFUNC)vn_default_error },
	{ &vop_lookup_desc, (VOPFUNC)fifo_lookup },		/* lookup */
	{ &vop_create_desc, (VOPFUNC)fifo_create },		/* create */
	{ &vop_mknod_desc, (VOPFUNC)fifo_mknod },		/* mknod */
	{ &vop_open_desc, (VOPFUNC)fifo_open },			/* open */
	{ &vop_close_desc, (VOPFUNC)hfsfifo_close },		/* close */
	{ &vop_access_desc, (VOPFUNC)hfs_access },		/* access */
	{ &vop_getattr_desc, (VOPFUNC)hfs_getattr },		/* getattr */
	{ &vop_setattr_desc, (VOPFUNC)hfs_setattr },		/* setattr */
	{ &vop_read_desc, (VOPFUNC)hfsfifo_read },		/* read */
	{ &vop_write_desc, (VOPFUNC)hfsfifo_write },		/* write */
	{ &vop_lease_desc, (VOPFUNC)fifo_lease_check },		/* lease */
	{ &vop_ioctl_desc, (VOPFUNC)fifo_ioctl },		/* ioctl */
	{ &vop_select_desc, (VOPFUNC)fifo_select },		/* select */
	{ &vop_revoke_desc, (VOPFUNC)fifo_revoke },		/* revoke */
	{ &vop_mmap_desc, (VOPFUNC)fifo_mmap },			/* mmap */
	{ &vop_fsync_desc, (VOPFUNC)hfs_fsync },		/* fsync */
	{ &vop_seek_desc, (VOPFUNC)fifo_seek },			/* seek */
	{ &vop_remove_desc, (VOPFUNC)fifo_remove },		/* remove */
	{ &vop_link_desc, (VOPFUNC)fifo_link },			/* link */
	{ &vop_rename_desc, (VOPFUNC)fifo_rename },		/* rename */
	{ &vop_mkdir_desc, (VOPFUNC)fifo_mkdir },		/* mkdir */
	{ &vop_rmdir_desc, (VOPFUNC)fifo_rmdir },		/* rmdir */
	{ &vop_symlink_desc, (VOPFUNC)fifo_symlink },		/* symlink */
	{ &vop_readdir_desc, (VOPFUNC)fifo_readdir },		/* readdir */
	{ &vop_readlink_desc, (VOPFUNC)fifo_readlink },		/* readlink */
	{ &vop_abortop_desc, (VOPFUNC)fifo_abortop },		/* abortop */
	{ &vop_inactive_desc, (VOPFUNC)hfs_inactive },		/* inactive */
	{ &vop_reclaim_desc, (VOPFUNC)hfs_reclaim },		/* reclaim */
	{ &vop_lock_desc, (VOPFUNC)hfs_lock },			/* lock */
	{ &vop_unlock_desc, (VOPFUNC)hfs_unlock },		/* unlock */
	{ &vop_bmap_desc, (VOPFUNC)fifo_bmap },			/* bmap */
	{ &vop_strategy_desc, (VOPFUNC)fifo_strategy },		/* strategy */
	{ &vop_print_desc, (VOPFUNC)hfs_print },		/* print */
	{ &vop_islocked_desc, (VOPFUNC)hfs_islocked },		/* islocked */
	{ &vop_pathconf_desc, (VOPFUNC)fifo_pathconf },		/* pathconf */
	{ &vop_advlock_desc, (VOPFUNC)fifo_advlock },		/* advlock */
	{ &vop_blkatoff_desc, (VOPFUNC)fifo_blkatoff },		/* blkatoff */
	{ &vop_valloc_desc, (VOPFUNC)fifo_valloc },		/* valloc */
	{ &vop_reallocblks_desc, (VOPFUNC)fifo_reallocblks },  /* reallocblks */
	{ &vop_vfree_desc, (VOPFUNC)err_vfree },		/* vfree */
	{ &vop_truncate_desc, (VOPFUNC)fifo_truncate },		/* truncate */
	{ &vop_update_desc, (VOPFUNC)hfs_update },		/* update */
	{ &vop_bwrite_desc, (VOPFUNC)hfs_bwrite },
	{ &vop_pagein_desc, (VOPFUNC)hfs_pagein },		/* Pagein */
	{ &vop_pageout_desc, (VOPFUNC)hfs_pageout },		/* Pageout */
    { &vop_copyfile_desc, (VOPFUNC)err_copyfile }, 		/* copyfile */
	{ &vop_blktooff_desc, (VOPFUNC)hfs_blktooff },		/* blktooff */
	{ &vop_offtoblk_desc, (VOPFUNC)hfs_offtoblk },		/* offtoblk */
  	{ &vop_cmap_desc, (VOPFUNC)hfs_cmap },			/* cmap */
	{ (struct vnodeop_desc*)NULL, (VOPFUNC)NULL }
};
struct vnodeopv_desc hfs_fifoop_opv_desc =
	{ &hfs_fifoop_p, hfs_fifoop_entries };
#endif /* FIFO */



