/*
 * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The 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, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

// AppleCDDAFileSystemVNodeOps.c created by CJS on Mon 10-Apr-2000

// Project Includes
#ifndef __APPLE_CDDA_FS_VNODE_OPS_H__
#include "AppleCDDAFileSystemVNodeOps.h"
#endif

#ifndef __APPLE_CDDA_FS_DEBUG_H__
#include "AppleCDDAFileSystemDebug.h"
#endif

#ifndef __APPLE_CDDA_FS_DEFINES_H__
#include "AppleCDDAFileSystemDefines.h"
#endif

#ifndef __APPLE_CDDA_FS_UTILS_H__
#include "AppleCDDAFileSystemUtils.h"
#endif

#ifndef __APPLE_CDDA_FS_VFS_OPS_H__
#include "AppleCDDAFileSystemVFSOps.h"
#endif

// System Includes
#include <vm/vm_pageout.h>

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/filedesc.h>
#include <sys/lock.h>
#include <sys/vnode.h>
#include <sys/dirent.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/malloc.h>
#include <sys/namei.h>
#include <sys/mbuf.h>
#include <sys/paths.h>
#include <sys/un.h>
#include <sys/unpcb.h>
#include <sys/errno.h>
#include <sys/ubc.h>
#include <vfs/vfs_support.h>

#include <miscfs/specfs/specdev.h>

extern int		strcmp __P  	( ( const char *, const char * ) );					// Kernel already includes a copy
extern int		strncmp __P  	( ( const char *, const char *, size_t length ) );	// Kernel already includes a copy
extern char *	strcpy __P  	( ( char *, const char * ) );						// Kernel already includes a copy
extern char *	strncpy __P 	( ( char *, const char *, size_t length ) );		// Kernel already includes a copy
extern char *	strchr __P  	( ( const char *, int ) );							// Kernel already includes a copy
extern int		atoi __P		( ( const char * ) );								// Kernel already includes a copy

#define RENAME_SUPPORTED 0


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	Static Function Prototypes
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

static SInt32
AddDirectoryEntry ( UInt32 nodeID, UInt8 type, const char * name, struct uio * uio );


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	AddDirectoryEntry -	This routine adds a directory entry to the uio buffer
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

static SInt32
AddDirectoryEntry ( UInt32 nodeID,
					UInt8 type,
					const char * name,
					struct uio * uio )
{
	
	struct dirent		directoryEntry;
	SInt32 				nameLength				= 0;
	UInt16 				directoryEntryLength	= 0;
	
	DebugAssert ( ( name != NULL ) );
	DebugAssert ( ( uio != NULL ) );

	DebugLog ( ( "fileName = %s\n", name ) );
	
	nameLength = strlen ( name );		
	DebugAssert ( ( nameLength < MAXNAMLEN + 1 ) );
	
	directoryEntry.d_fileno = nodeID;
	directoryEntry.d_reclen = sizeof ( directoryEntry );
	directoryEntry.d_type 	= type;
	directoryEntry.d_namlen = nameLength;
	directoryEntryLength	= directoryEntry.d_reclen;
	
	// Copy the string
	strncpy ( directoryEntry.d_name, name, MAXNAMLEN );
	
	// Zero the rest of the array for safe-keeping
	bzero ( &directoryEntry.d_name[nameLength], MAXNAMLEN + 1 - nameLength );
	
	if ( uio->uio_resid < directoryEntry.d_reclen )
	{
		
		// We can't copy because there isn't enough room in the buffer,
		// so set the directoryEntryLength to zero so the caller knows
		// an error occurred
		directoryEntryLength = 0;
		
	}
	
	else
	{
		
		// Move the data
		uiomove ( ( caddr_t ) &directoryEntry, sizeof ( directoryEntry ), uio );
				
	}
	
	return directoryEntryLength;
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Lookup -	This routine performs a lookup
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Lookup ( struct vop_lookup_args * lookupArgsPtr )
/*
struct vop_lookup_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_dvp;
	struct vnode **a_vpp;
	struct componentname *a_cnp;
};
*/
{
	
	struct componentname *		compNamePtr 		= NULL;
	struct vnode **				vNodeHandle 		= NULL;
	struct vnode *				parentVNodePtr 		= NULL;
	struct vnode *				vNodePtr 			= NULL;
	struct proc *				theProcPtr 			= NULL;
	AppleCDDANodePtr			parentCDDANodePtr	= NULL;
	AppleCDDAMountPtr			cddaMountPtr		= NULL;
	AppleCDDANodeInfoPtr		nodeInfoArrayPtr	= NULL;
	UInt32						index				= 0;
	int 						error				= 0;
	int 						flags				= 0;
	int							lockParent			= 0;
	
	DebugLog ( ( "CDDA_Lookup: Entering.\n" ) );
	
	DebugAssert ( ( lookupArgsPtr != NULL ) );
	
	compNamePtr 	= lookupArgsPtr->a_cnp;
	vNodeHandle 	= lookupArgsPtr->a_vpp;
	parentVNodePtr 	= lookupArgsPtr->a_dvp;
	
	DebugAssert ( ( compNamePtr != NULL ) );
	DebugAssert ( ( vNodeHandle != NULL ) );
	DebugAssert ( ( parentVNodePtr != NULL ) );
	
	theProcPtr 			= compNamePtr->cn_proc;
	parentCDDANodePtr	= VTOCDDA ( parentVNodePtr );
	cddaMountPtr 		= VFSTOCDDA ( parentVNodePtr->v_mount );
	
	DebugAssert ( ( theProcPtr != NULL ) );
	DebugAssert ( ( parentCDDANodePtr != NULL ) );
	DebugAssert ( ( cddaMountPtr != NULL ) );
	
	*vNodeHandle 	= NULL;
	flags			= compNamePtr->cn_flags;
	lockParent		= flags & LOCKPARENT;


#if RENAME_SUPPORTED
	
	// Check if process wants to create, delete or rename anything
	if ( compNamePtr->cn_nameiop == CREATE )
	{
		
		DebugLog ( ( "Can't CREATE %s, returning EROFS\n", compNamePtr->cn_nameptr ) );
		error = EROFS;
		goto Exit;
		
	}
	
	// Check if process wants to create, delete or rename anything
	if ( compNamePtr->cn_nameiop == RENAME )
	{
		
		DebugLog ( ( "Can't RENAME %s, returning EJUSTRETURN\n", compNamePtr->cn_nameptr ) );
		error = EJUSTRETURN;
		goto Exit;
		
	}

	// Check if process wants to create, delete or rename anything
	if ( compNamePtr->cn_nameiop == DELETE )
	{
		
		DebugLog ( ( "Can't DELETE %s\n", compNamePtr->cn_nameptr ) );
		
	}
	
#else /* if ( RENAME_SUPPORTED = 0 ) */
	
	// Check if process wants to create, delete or rename anything
	if ( compNamePtr->cn_nameiop == CREATE ||
		 compNamePtr->cn_nameiop == RENAME ||
		 compNamePtr->cn_nameiop == DELETE )
	{
		
		DebugLog ( ( "Can't CREATE %s, returning EROFS\n", compNamePtr->cn_nameptr ) );
		error = EROFS;
		goto Exit;
		
	}
	
#endif
	
	
	// Determine if we're looking for a resource fork.
	// NB: this could cause a read off the end of the component name buffer in some rare cases.
	if ( ( flags & ISLASTCN ) == 0 && bcmp ( &compNamePtr->cn_nameptr[compNamePtr->cn_namelen],
											 _PATH_RSRCFORKSPEC,
											 sizeof ( _PATH_RSRCFORKSPEC ) - 1 ) == 0 )
	{
		
		DebugLog ( ( "No resource forks available, return ENOTDIR.\n" ) );
		compNamePtr->cn_consume = sizeof ( _PATH_RSRCFORKSPEC ) - 1;
		error = ENOTDIR;
		goto Exit;
		
	}
	
	if ( parentVNodePtr->v_type != VDIR )
	{
		
		DebugLog ( ( "parentVNodePtr->v_type != VDIR, returning ENOTDIR\n" ) );
		error = ENOTDIR;
		goto Exit;
		
	}
	
	DebugLog ( ( "Looking for name = %s.\n", compNamePtr->cn_nameptr ) );
	
	// first check for ".", "..", and ".TOC.plist"
	if ( compNamePtr->cn_nameptr[0] == '.' )
	{
		
		if ( compNamePtr->cn_namelen == 1 )
		{
			
			DebugLog ( ( ". was requested\n" ) );
			
			// "." requested
			vNodePtr = parentVNodePtr;
			VREF ( vNodePtr );
			
			error = 0;
			*vNodeHandle = vNodePtr;
			
			goto Exit;
			
		}
		
		else if ( ( compNamePtr->cn_namelen == 2 ) && ( compNamePtr->cn_nameptr[1] == '.' ) )
		{
			
			panic ( "CDDA_Lookup: namei asked for .. when it shouldn't have" );			
			
		}
		
		else if ( ( compNamePtr->cn_namelen == 10 ) && ( !strncmp ( &compNamePtr->cn_nameptr[1], "TOC.plist", 9 ) ) )
		{
			
			DebugLog ( ( ".TOC.plist was requested\n" ) );
			
			// ".TOC.plist" requested, lock it and return
			error = vget ( cddaMountPtr->xmlFileVNodePtr, LK_EXCLUSIVE, theProcPtr );
			if ( error != 0 )
			{
				
				DebugLog ( ( "CDDA_Lookup: exiting with error = %d after vget.\n", error ) );
				goto Exit;
				
			}
						
			if ( !lockParent || !( flags & ISLASTCN ) )
			{
				
				// Unlock the parent if the last component name
				VOP_UNLOCK ( parentVNodePtr, 0, theProcPtr );
				
			}
			
			if ( ! ( flags & LOCKLEAF ) )
			{
				
				// Unlock the vnode since they didn't ask for it locked
				VOP_UNLOCK ( cddaMountPtr->xmlFileVNodePtr, 0, theProcPtr );
				
			}
			
			// Stuff the vnode in
			*vNodeHandle = cddaMountPtr->xmlFileVNodePtr;
			
			goto Exit;
			
		}
		
	}
	
	// Now check for the name of the root directory (usually "Audio CD", but could be anything)
	if ( strcmp ( &compNamePtr->cn_nameptr[0], parentCDDANodePtr->u.directory.name ) == 0 )
	{
		
		DebugLog ( ( "The root directory was requested by name = %s\n", parentCDDANodePtr->u.directory.name ) );
		
		// "." requested
		vNodePtr = parentVNodePtr;
		VREF ( vNodePtr );
		
		error = 0;
		*vNodeHandle = vNodePtr;
		
		goto Exit;
		
	}
	
	// Look in our NodeInfo array to see if a vnode has been created for this
	// track yet.
	nodeInfoArrayPtr = VFSTONODEINFO ( parentVNodePtr->v_mount );
	DebugAssert ( ( nodeInfoArrayPtr != NULL ) );
	
	// Not '.' or "..", so it must be a track we're looking up
	// Look for entries by name (making sure the entry's length matches the
	// compNamePtr's namelen
	
	
LOOP:
	
	
	DebugLog ( ( "Locking nodeInfoLock.\n" ) );
	
	error = lockmgr ( &cddaMountPtr->nodeInfoLock, LK_EXCLUSIVE, NULL, theProcPtr );
	
	index = 0;
	
	while ( index < ( parentCDDANodePtr->u.directory.entryCount - kNumberOfFakeDirEntries ) )
	{
				
		if ( nodeInfoArrayPtr->nameSize == compNamePtr->cn_namelen &&
			 !strcmp ( nodeInfoArrayPtr->name, compNamePtr->cn_nameptr ) )
		{
			
			// See if the vNodePtr was attached (vNodePtr is only non-NULL if the node has been created)
			if ( nodeInfoArrayPtr->vNodePtr != NULL )
			{
								
				// If the vnode was attached, the vNode was created already
				// so set the pointer to that address
				vNodePtr = nodeInfoArrayPtr->vNodePtr;
				
				DebugLog ( ( "Releasing nodeInfoLock.\n" ) );

				// Release the lock on our nodeInfo structure first
				error = lockmgr ( &cddaMountPtr->nodeInfoLock, LK_RELEASE, NULL, theProcPtr );
				
				// vget the vnode to up the refcount and lock it
				error = vget ( vNodePtr, LK_EXCLUSIVE, theProcPtr );
				if ( error != 0 )
				{

					DebugLog ( ( "CDDA_Lookup: exiting with error = %d after vget.\n", error ) );
					goto LOOP;
				
				}
								
				if ( !lockParent || !( flags & ISLASTCN ) )
				{
					
					// Unlock the parent if the last component name
					VOP_UNLOCK ( parentVNodePtr, 0, theProcPtr );
					
				}
				
				if ( ! ( flags & LOCKLEAF ) )
				{
					
					// Unlock the vnode since they didn't ask for it locked
					VOP_UNLOCK ( vNodePtr, 0, theProcPtr );
				
				}

				// Stuff the vnode in
				*vNodeHandle = vNodePtr;
					
				// The specified vNode was found and successfully acquired
				goto Exit;
			
			}
			
			else
			{
				
				int		error2 = 0;
				
				DebugLog ( ( "Couldn't find the vnode...Calling CreateNewCDDAFile\n" ) );

				DebugLog ( ( "Releasing nodeInfoLock.\n" ) );
				// Now we can release our lock because we're creating a node
				error = lockmgr ( &cddaMountPtr->nodeInfoLock, LK_RELEASE, NULL, theProcPtr );

				// if we get here, it doesn't exist yet, so create it
				error2 = CreateNewCDDAFile ( parentVNodePtr->v_mount,
											nodeInfoArrayPtr->trackDescriptor.point + kOffsetForFiles,
											nodeInfoArrayPtr,
											theProcPtr,
											&vNodePtr );

				DebugLog ( ( "Locking nodeInfoLock.\n" ) );
				error = lockmgr ( &cddaMountPtr->nodeInfoLock, LK_EXCLUSIVE, NULL, theProcPtr );
				
				// Make sure we mark this vnode as being in the array now
				nodeInfoArrayPtr->vNodePtr = vNodePtr;
				
				// Now we can release our lock because we're getting out
				error = lockmgr ( &cddaMountPtr->nodeInfoLock, LK_RELEASE, NULL, theProcPtr );
				
				if ( error != 0 || error2 != 0 )
					goto Exit;
				
				if ( !lockParent || !( flags & ISLASTCN ) )
				{
					
					// Unlock the parent if the last component name
					VOP_UNLOCK ( parentVNodePtr, 0, theProcPtr );
					
				}

				if ( ! ( flags & LOCKLEAF ) )
				{
					
					// Unlock the vnode since they didn't ask for it locked
					VOP_UNLOCK ( vNodePtr, 0, theProcPtr );
				
				}

				// Stuff the vnode in
				*vNodeHandle = vNodePtr;

				// The specified vNode was found and successfully acquired
				goto Exit;
				
			}
			
		}
		
		index++;
		nodeInfoArrayPtr++;
		
	}
	
	DebugLog ( ( "Releasing nodeInfoLock...About to return ENOENT.\n" ) );
	
	// Now we can release our lock because we're getting out
	error = lockmgr ( &cddaMountPtr->nodeInfoLock, LK_RELEASE, NULL, theProcPtr );
	
	// If we get here, we couldn't find anything with that name. Return ENOENT
	return ( ENOENT );
	
	
	
Exit:
	
	
	DebugLog ( ( "CDDA_Lookup: exiting from Exit with error = %d.\n", error ) );
	
	return ( error );
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Open -	This routine opens a file
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Open ( struct vop_open_args * openArgsPtr )
/*
struct vop_open_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	int a_mode;
	struct ucred *a_cred;
	struct proc *a_p;
};
*/
{

	struct vnode *		vNodePtr 	= NULL;
	int 				error		= 0;
	
	DebugLog ( ( "CDDA_Open: Entering.\n" ) );

	DebugAssert ( ( openArgsPtr != NULL ) );
	
	vNodePtr = openArgsPtr->a_vp;
	DebugAssert ( ( vNodePtr != NULL ) );
		
	// Set the vNodeOperationType to tell the user process if we are going to open a
	// file or a directory
	if ( vNodePtr->v_type != VREG && vNodePtr->v_type != VDIR )
	{
	
		// This should never happen but just in case
		DebugLog ( ( "Error = %d, wrong v_type.\n", ENOTSUP ) );
		error = ENOTSUP;
		goto ERROR;
		
	}
	
	// Turn off speculative read-ahead for our vnodes. The cluster
	// code can't possibly do the right thing when we have possible
	// loss of streaming on CD media.
	vNodePtr->v_flag |= VRAOFF;
	
	
ERROR:


	DebugLog ( ( "CDDA_Open: exiting with error = %d.\n", error ) );

	return ( error );

}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Close -	This routine closes a file. Since we are a read-only
//					filesystem, we don't have any cleaning up to do.
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Close ( struct vop_close_args * closeArgsPtr )
/*
struct vop_close_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	int a_fflag;
	struct ucred *a_cred;
	struct proc *a_p;
};
*/
{
	
	DebugLog ( ( "CDDA_Close: Entering.\n" ) );

#if DEBUG
	DebugAssert ( ( closeArgsPtr != NULL ) );
#else
	#pragma unused ( closeArgsPtr )
#endif
	
	DebugLog ( ( "CDDA_Close: exiting...\n" ) );
	
	return ( 0 );
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Read -	This routine reads from a file
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Read ( struct vop_read_args * readArgsPtr )
/*
struct vop_read_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	struct uio *a_uio;
	int a_ioflag;
	struct ucred *a_cred;
};
*/
{
	
	register struct vnode * 	vNodePtr 			= NULL;
	register struct uio *		uio 				= NULL;
	AppleCDDANodePtr			cddaNodePtr			= NULL;
	int							error				= 0;
	
	DebugLog ( ( "CDDA_Read: Entering.\n" ) );
	
	DebugAssert ( ( readArgsPtr ) );
	
	vNodePtr	= readArgsPtr->a_vp;
	uio			= readArgsPtr->a_uio;
	
	DebugAssert ( ( vNodePtr != NULL ) );
	DebugAssert ( ( uio != NULL ) );
	DebugAssert ( ( UBCINFOEXISTS ( vNodePtr ) != 0 ) );
	
	cddaNodePtr = VTOCDDA ( vNodePtr );
	DebugAssert ( ( cddaNodePtr != NULL ) );
	
	cddaNodePtr->flags |= kAppleCDDAAccessedMask;
	
	// Check to make sure we're operating on a regular file
	if ( vNodePtr->v_type != VREG )
	{

		DebugLog ( ( "CDDA_Read: not a file, exiting with error = %d.\n", EISDIR ) );
		return ( EISDIR );

	}
	
	// Check to make sure they asked for data
	if ( uio->uio_resid == 0 )
	{
		
		DebugLog ( ( "CDDA_Read: uio_resid = 0, no data requested" ) );
		return ( 0 );
		
	}
	
	// Can't read from a negative offset
	if ( uio->uio_offset < 0 )
	{
		
		DebugLog ( ( "CDDA_Read: Can't read from a negative offset..." ) );
		return ( EINVAL );
		
	}

	if ( cddaNodePtr->nodeType == kAppleCDDAXMLFileType )
	{
		
		off_t		offset			= uio->uio_offset;
		UInt32		amountToCopy	= 0;
		UInt32		numBytes		= 0;
		
		numBytes = cddaNodePtr->u.xmlFile.fileSize;
		
		// Check to make sure we don't read past EOF
		if ( uio->uio_offset > numBytes )
		{
			
			DebugLog ( ( "CDDA_Read: Can't read past end of file..." ) );
			return ( 0 );
			
		}
		
		amountToCopy = ulmin ( uio->uio_resid, numBytes - offset );
		
		uiomove ( ( caddr_t ) &cddaNodePtr->u.xmlFile.fileDataPtr[offset],
				  amountToCopy,
				  uio );
		
		return ( 0 );
		
	}
	
	else if ( cddaNodePtr->nodeType == kAppleCDDATrackType )
	{
		
		UInt32			headerSize		= 0;
		UInt32			count			= 0;
		UInt32			blockNumber 	= 0;
		off_t			offset			= 0;
		off_t			sectorOffset	= 0;
		struct buf *	bufPtr			= NULL;
		
		offset	= uio->uio_offset;
		
		// Check to make sure we don't read past EOF
		if ( offset > cddaNodePtr->u.file.nodeInfoPtr->numBytes )
		{
			
			DebugLog ( ( "CDDA_Read: Can't read past end of file..." ) );
			return ( 0 );
			
		}
		
		headerSize = sizeof ( cddaNodePtr->u.file.aiffHeader );
		
		// Copy any part of the header that we need to copy.
		if ( offset < headerSize )
		{
			
			UInt32	amountToCopy = 0;
			
			amountToCopy = ulmin ( uio->uio_resid, headerSize - offset );
			
			uiomove ( ( caddr_t ) &cddaNodePtr->u.file.aiffHeader.u.alignedHeader.filler[offset],
				  amountToCopy,
				  uio );
			
			offset += amountToCopy;
			
		}
		
		if ( ( uio->uio_resid > 0  ) &&
			 ( uio->uio_offset < cddaNodePtr->u.file.nodeInfoPtr->numBytes ) )
		{
			
			// Adjust offset by the header size so we have a true offset into the media.
			offset -= headerSize;
			sectorOffset = offset % kPhysicalMediaBlockSize;
			blockNumber = ( offset / kPhysicalMediaBlockSize ) + cddaNodePtr->u.file.nodeInfoPtr->LBA;
			
			// Part 1
			// We do the read in 3 parts. First is the portion which is not part of a full 2352 byte block.
			// In some cases, we actually are sector aligned and we skip this part. A lot of times we'll find
			// this sector incore as well, since it was part of third read for the previous I/O.
			if ( sectorOffset != 0 )
			{
				
				// Clip to requested transfer count and end of file.
				count = ulmin ( uio->uio_resid, ( kPhysicalMediaBlockSize - sectorOffset ) );
				count = ulmin ( count, cddaNodePtr->u.file.nodeInfoPtr->numBytes - uio->uio_offset );
				
				// Read the one sector
				error = bread ( cddaNodePtr->blockDeviceVNodePtr,
								blockNumber,
								kPhysicalMediaBlockSize,
								NOCRED,
								&bufPtr );
				
				if ( error != 0 )
				{
					
					brelse ( bufPtr );
					return ( error );
					
				}
				
				// Move the data from the block into the buffer
				uiomove ( bufPtr->b_data + sectorOffset, count, uio );
				
				// Make sure we mark this bp invalid as we don't need to keep it around anymore
				SET ( bufPtr->b_flags, B_INVAL );
				
				// Release this buffer back into the buffer pool. 
				brelse ( bufPtr );
				
				// Update offset
				blockNumber++;
				
			}
			
			// Part 2
			// Now we execute the second part of the read. This will be the largest chunk of the read.
			// We will read multiple disc blocks up to MAXBSIZE bytes in a loop until we hit a chunk which
			// is less than one block size. That will be read in the third part.
			
			while ( ( uio->uio_resid > kPhysicalMediaBlockSize ) &&
					( uio->uio_offset < cddaNodePtr->u.file.nodeInfoPtr->numBytes ) )
			{
				
				UInt32		blocksToRead = 0;
				
				// Read in as close to MAXBSIZE chunks as possible
				if ( uio->uio_resid > kMaxBytesPerRead )
				{
					blocksToRead	= kMaxBlocksPerRead;
					count			= kMaxBytesPerRead;
				}
				
				else
				{
					blocksToRead	= uio->uio_resid / kPhysicalMediaBlockSize;
					count			= blocksToRead * kPhysicalMediaBlockSize;
				}
				
				// bread kMaxBlocksPerRead blocks and put them in the cache.
				error = bread ( cddaNodePtr->blockDeviceVNodePtr,
								blockNumber,
								count,
								NOCRED,
								&bufPtr );
				
				if ( error != 0 )
				{
					
					brelse ( bufPtr );
					return ( error );
					
				}
				
				count = ulmin ( count, cddaNodePtr->u.file.nodeInfoPtr->numBytes - uio->uio_offset );
				
				// Move the data from the block into the buffer
				uiomove ( bufPtr->b_data, count, uio );
				
				// Make sure we mark any intermediate buffers as invalid as we don't need
				// to keep them.
				SET ( bufPtr->b_flags, B_INVAL );
				
				// Release this buffer back into the buffer pool. 
				brelse ( bufPtr );
				
				// Update offset
				blockNumber += blocksToRead;
				
			}
			
			// Part 3
			// Now that we have read everything, we read the tail end which is a partial sector.
			// Sometimes we don't need to execute this step since there isn't a tail.
			if ( ( uio->uio_resid > 0  ) &&
				 ( uio->uio_offset < cddaNodePtr->u.file.nodeInfoPtr->numBytes ) )
			{
				
				count = ulmin ( uio->uio_resid, cddaNodePtr->u.file.nodeInfoPtr->numBytes - uio->uio_offset );
				
				// Read the one sector
				error = bread ( cddaNodePtr->blockDeviceVNodePtr,
								blockNumber,
								kPhysicalMediaBlockSize,
								NOCRED,
								&bufPtr );
				
				if ( error != 0 )
				{
					
					brelse ( bufPtr );
					return ( error );
					
				}
				
				// Move the data from the block into the buffer
				uiomove ( bufPtr->b_data, count, uio );
				
				// Release this buffer back into the buffer pool. 
				brelse ( bufPtr );
				
			}
			
		}
		
	}
	
	DebugLog ( ( "CDDA_Read: exiting.\n" ) );
	
	return ( error );
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_ReadDir -	This routine reads the contents of a directory
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_ReadDir ( struct vop_readdir_args * readDirArgsPtr )
/*
struct vop_readdir_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	struct uio *a_uio;
	struct ucred *a_cred;
	int *a_eofflag;
	int *a_ncookies;
	u_long **a_cookies;
};
*/
{

	struct vnode * 			vNodePtr 			= NULL;
	AppleCDDANodePtr	 	cddaNodePtr			= NULL;
	AppleCDDAMountPtr	 	cddaMountPtr		= NULL;
	AppleCDDANodeInfoPtr	nodeInfoArrayPtr	= NULL;
	struct proc *			theProcPtr			= NULL;
	struct uio *			uio 				= NULL;
	UInt32					index				= 0;
	int 					error				= 0;
	SInt32					offsetValue			= 0;
	UInt32					direntSize			= 0;

	DebugLog ( ( "CDDA_ReadDir: Entering.\n" ) );

	DebugAssert ( ( readDirArgsPtr != NULL ) );

	vNodePtr 	= readDirArgsPtr->a_vp;
	uio 		= readDirArgsPtr->a_uio;

	DebugAssert ( ( vNodePtr != NULL ) );
	DebugAssert ( ( uio != NULL ) );

	// Get the current proc
	theProcPtr = current_proc ( );
	cddaNodePtr = VTOCDDA ( vNodePtr );
	
	DebugAssert ( ( theProcPtr != NULL ) );
	DebugAssert ( ( cddaNodePtr != NULL ) );

	// We don't allow exporting CDDA mounts, and currently local requests do
	// not need cookies.
	if ( readDirArgsPtr->a_ncookies != NULL || readDirArgsPtr->a_cookies != NULL )
	{
		
		DebugLog ( ( "No cookie exporting, exiting with error = %d.\n", EINVAL ) );
		return EINVAL;

	}
	
	// First make sure it is a directory we are dealing with
	if ( vNodePtr->v_type != VDIR )
	{

		DebugLog ( ( "CDDA_ReadDir: not a directory, exiting with error = %d.\n", ENOTDIR ) );
		return ( ENOTDIR );

	}
	
	if ( cddaNodePtr->nodeID != kAppleCDDARootFileID )
	{

		DebugLog ( ( "CDDA_ReadDir: not root directory, exiting with error = %d.\n", EINVAL ) );
		return ( EINVAL );

	}
	
	// Make sure it's all one big buffer
	if ( uio->uio_iovcnt > 1 )
	{

		DebugLog ( ( "More than one buffer, exiting with error = %d.\n", EINVAL ) );
		return ( EINVAL );

	}

	// Make sure we don't return partial entries
	if ( uio->uio_resid < sizeof ( struct dirent ) )
	{

		DebugLog ( ( "resid < dirent size, exiting with error = %d.\n", EINVAL ) );
		return ( EINVAL );

	}
	
	direntSize 	= sizeof ( struct dirent );
	
	// Synthesize '.', "..", and ".TOC.plist"
	if ( uio->uio_offset == 0 )
	{
	
		offsetValue = AddDirectoryEntry ( cddaNodePtr->nodeID, DT_DIR, ".", uio );
		if ( offsetValue == 0 )
		{
		
			DebugLog ( ( "offsetValue is zero, exiting with error = %d.\n", 0 ) );
			return 0;
		
		}
			
	}
		
	if ( uio->uio_offset == direntSize )
	{
		
		offsetValue = AddDirectoryEntry ( cddaNodePtr->nodeID, DT_DIR, "..", uio );
		if ( offsetValue == 0 )
		{
		
			DebugLog ( ( "offsetValue is zero, exiting with error = %d.\n", 0 ) );
			return 0;
		
		}
		
	}
		
	
	if ( uio->uio_offset == direntSize * kAppleCDDARootFileID )
	{
		
		offsetValue += AddDirectoryEntry ( kAppleCDDAXMLFileID, kAppleCDDAXMLFileType, ".TOC.plist", uio );
		if ( offsetValue == 0 )
		{
		
			DebugLog ( ( "offsetValue is zero, exiting with error = %d.\n", 0 ) );
			return 0;
		
		}
		
	}
	
	nodeInfoArrayPtr	= VFSTONODEINFO ( vNodePtr->v_mount );
	cddaMountPtr		= VFSTOCDDA ( vNodePtr->v_mount );

	DebugAssert ( ( nodeInfoArrayPtr != NULL ) );
	DebugAssert ( ( cddaMountPtr != NULL ) );
	
	DebugLog ( ( "cddaMountPtr->numTracks = %ld.\n", cddaMountPtr->numTracks ) );
	DebugLog ( ( "buffer size needed = %ld.\n", direntSize * ( cddaMountPtr->numTracks + kNumberOfFakeDirEntries ) ) );
	
	// OK, so much for the fakes.  Now for the "real thing"
	// Loop over all the names in the NameArray to produce directory entries
	for ( index = 0; index < cddaMountPtr->numTracks; index++, nodeInfoArrayPtr++ )
	{
		
		DebugLog ( ( "uio->uio_offset = %ld.\n", uio->uio_offset ) );
		DebugLog ( ( "uio->uio_resid = %ld.\n", uio->uio_resid ) );
		
		if ( uio->uio_offset == direntSize * ( index + kNumberOfFakeDirEntries ) )
		{
			
			DebugLog ( ( "index = %ld.\n", index ) );
								
			// Return this entry
			offsetValue = AddDirectoryEntry ( nodeInfoArrayPtr->trackDescriptor.point,
											  kAppleCDDATrackType,
											  nodeInfoArrayPtr->name,
											  uio );
			
			if ( offsetValue == 0 )
			{
			
				DebugLog ( ( "offsetValue is zero, exiting with error = %d.\n", 0 ) );
				return 0;
			
			}
						
		}
				
	}
		
	if ( readDirArgsPtr->a_eofflag )
	{
		
		DebugLog ( ( "eofflag = %d.\n", ( uio->uio_offset >= direntSize * ( cddaMountPtr->numTracks + kNumberOfFakeDirEntries ) ) ? 1 : 0 ) );
		
		// If we ran all the way through the list, there are no more
		*readDirArgsPtr->a_eofflag = ( uio->uio_offset >= direntSize * ( cddaMountPtr->numTracks + kNumberOfFakeDirEntries ) ) ? 1 : 0;
		error = 0;
		
	}
	
	DebugLog ( ( "CDDA_ReadDir: exiting with error = %d.\n", error ) );
	
	return ( error );
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_PageIn -	This routine handles VM PageIn requests
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_PageIn ( struct vop_pagein_args * pageInArgsPtr )
/*
struct vop_pagein_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	upl_t a_pl;
	vm_offset_t a_pl_offset;
	off_t a_f_offset;
	size_t a_size;
	struct ucred *a_cred;
	int a_flags;
};
*/
{
	
	register struct vnode *		vNodePtr		= NULL;
	AppleCDDANodePtr			cddaNodePtr		= NULL;
	int 		   				error			= 0;
	int							nocommit 		= 0;
	UInt32						numBytes		= 0;
	
	DebugLog ( ( "CDDA_PageIn: Entering.\n" ) );
	
	DebugAssert ( ( pageInArgsPtr != NULL ) );
	
	vNodePtr = pageInArgsPtr->a_vp;
	nocommit = pageInArgsPtr->a_flags & UPL_NOCOMMIT;
	
	DebugAssert ( ( vNodePtr != NULL ) );
	
	cddaNodePtr = VTOCDDA ( vNodePtr );
	DebugAssert ( ( cddaNodePtr != NULL ) );
	
	if ( vNodePtr->v_type != VREG )
		panic ( "CDDA_PageIn: vNodePtr not UBC type.\n" );
	
	if ( cddaNodePtr->nodeType == kAppleCDDAXMLFileType )
	{
		
		numBytes = cddaNodePtr->u.xmlFile.fileSize;
				
	}
	
	else if ( cddaNodePtr->nodeType == kAppleCDDATrackType )
	{
	
		numBytes = cddaNodePtr->u.file.nodeInfoPtr->numBytes;
	
	}

	// If they didn't ask for any data, then we are done
	if ( pageInArgsPtr->a_size == 0 )
	{
		
		if ( !nocommit )
		{
			
			ubc_upl_abort_range ( pageInArgsPtr->a_pl,
								  pageInArgsPtr->a_pl_offset,
								  pageInArgsPtr->a_size,
								  UPL_ABORT_ERROR | UPL_ABORT_FREE_ON_EMPTY );
			
		}
		
		return ( error );
		
	}
	
	// Make sure we aren't reading from a negative offset
	if ( pageInArgsPtr->a_f_offset < 0 )
	{
		
		if ( !nocommit )
		{
			
			ubc_upl_abort_range ( pageInArgsPtr->a_pl,
								  pageInArgsPtr->a_pl_offset,
								  pageInArgsPtr->a_size,
								  UPL_ABORT_ERROR | UPL_ABORT_FREE_ON_EMPTY );
			
		}
		
		error = EINVAL;
		DebugLog ( ( "CDDA_PageIn: trying to page in from a negative offset.\n" ) );
		
		return ( error );
		
	}
	
	// Check to make sure we don't read past EOF
	if ( pageInArgsPtr->a_f_offset > numBytes )
	{
		
		if ( !nocommit )
		{
			
			ubc_upl_abort_range ( pageInArgsPtr->a_pl,
								  pageInArgsPtr->a_pl_offset,
								  pageInArgsPtr->a_size,
								  UPL_ABORT_ERROR | UPL_ABORT_FREE_ON_EMPTY );
			
		}
		
		return ( error );
		
	}
	
	// Workaround for faked ".TOC.plist" file
	if ( cddaNodePtr->nodeType == kAppleCDDAXMLFileType )
	{
				
		kern_return_t		kret			= 0;
		vm_offset_t			vmOffsetPtr		= 0;
		off_t				amountToCopy	= 0;
					
		// Map the physical page into the kernel address space
		kret = ubc_upl_map ( pageInArgsPtr->a_pl, &vmOffsetPtr );
		
		// If we got an error or the vmOffsetPtr is zero, panic for now
		if ( kret != KERN_SUCCESS || vmOffsetPtr == 0 )
		{
		
			panic ( "CDDA_PageIn: error mapping buffer into kernel space!" );
		
		}
		
		// Zero fill the page
		bzero ( ( caddr_t )( vmOffsetPtr + pageInArgsPtr->a_pl_offset ), PAGE_SIZE );
		
		amountToCopy = ulmin ( PAGE_SIZE, numBytes - pageInArgsPtr->a_f_offset );
		
		// Copy the file data
		bcopy ( &cddaNodePtr->u.xmlFile.fileDataPtr[pageInArgsPtr->a_f_offset],
				( void * ) vmOffsetPtr,
				amountToCopy );
		
		// Unmap the physical page from the kernel address space
		kret = ubc_upl_unmap ( pageInArgsPtr->a_pl );
		
		// If we got an error, panic for now
		if ( kret != KERN_SUCCESS )
		{
		
			panic ( "CDDA_PageIn: error unmapping buffer from kernel space!" );
		
		}
		
		if ( !nocommit )
		{
			
			// Commit the page to the vm subsystem
			ubc_upl_commit_range ( 	pageInArgsPtr->a_pl,
									pageInArgsPtr->a_pl_offset,
									PAGE_SIZE,
									UPL_COMMIT_FREE_ON_EMPTY | UPL_COMMIT_CLEAR_DIRTY );
			
		}
		
		return 0;
			
	}
	
	else if ( cddaNodePtr->nodeType == kAppleCDDATrackType )
	{
		
		UInt32			headerSize		= 0;
		UInt32			blockNumber 	= 0;
		UInt32			count			= 0;
		off_t			offset			= 0;
		off_t			sectorOffset	= 0;
		off_t			residual		= 0;
		kern_return_t	kret			= 0;
		vm_offset_t		vmOffsetPtr		= 0;
		struct buf *	bufPtr			= NULL;
		
		residual	= pageInArgsPtr->a_size;
		offset		= pageInArgsPtr->a_f_offset;
		
		// Check to make sure we don't read past EOF
		if ( offset > cddaNodePtr->u.file.nodeInfoPtr->numBytes )
		{
			
			DebugLog ( ( "CDDA_PageIn: Can't read past end of file..." ) );
			return ( 0 );
			
		}
		
		headerSize = sizeof ( cddaNodePtr->u.file.aiffHeader );

		// Map the physical pages into the kernel address space
		kret = ubc_upl_map ( pageInArgsPtr->a_pl, &vmOffsetPtr );
		
		// If we got an error or the vmOffsetPtr is zero, panic for now
		if ( kret != KERN_SUCCESS || vmOffsetPtr == 0 )
		{
		
			panic ( "CDDA_PageIn: error mapping buffer into kernel space!" );
		
		}
		
		// Copy any part of the header that we need to copy.
		if ( offset < headerSize )
		{
			
			off_t	amountToCopy	= 0;
			
			amountToCopy = ulmin ( pageInArgsPtr->a_size, headerSize - offset );			
			
			// Copy the header data
			bcopy ( &cddaNodePtr->u.file.aiffHeader.u.alignedHeader.filler[pageInArgsPtr->a_f_offset],
					( void * ) vmOffsetPtr,
					amountToCopy );
			
			offset += amountToCopy;
			residual -= amountToCopy;
			
		}
		
		if ( ( residual > 0 ) &&
			 ( offset < cddaNodePtr->u.file.nodeInfoPtr->numBytes ) )
		{
			
			// Adjust offset by the header size so we have a true offset into the media.
			offset -= headerSize;
			sectorOffset = offset % kPhysicalMediaBlockSize;
			blockNumber = ( offset / kPhysicalMediaBlockSize ) + cddaNodePtr->u.file.nodeInfoPtr->LBA;
			
			// Part 1
			// We do the read in 3 parts. First is the portion which is not part of a full 2352 byte block.
			// In some cases, we actually are sector aligned and we skip this part. A lot of times we'll find
			// this sector incore as well, since it was part of third read for the previous I/O.
			if ( sectorOffset != 0 )
			{
				
				// Clip to requested transfer count and end of file.
				count = ulmin ( residual, ( kPhysicalMediaBlockSize - sectorOffset ) );
				count = ulmin ( count, cddaNodePtr->u.file.nodeInfoPtr->numBytes - offset );
				
				// Read the one sector
				error = bread ( cddaNodePtr->blockDeviceVNodePtr,
								blockNumber,
								kPhysicalMediaBlockSize,
								NOCRED,
								&bufPtr );
				
				if ( error != 0 )
				{
					
					brelse ( bufPtr );
					return ( error );
					
				}
				
				// Copy the data
				bcopy ( bufPtr->b_data + sectorOffset, ( void * ) vmOffsetPtr + offset + headerSize, count );
				
				// Increment/decrement counters
				offset		+= count;
				residual	-= count;
				
				// Make sure we mark this bp invalid as we don't need to keep it around anymore
				SET ( bufPtr->b_flags, B_INVAL );
				
				// Release this buffer back into the buffer pool. 
				brelse ( bufPtr );
				
				// Update offset
				blockNumber++;
				
			}
			
			// Part 2
			// Now we execute the second part of the read. This will be the largest chunk of the read.
			// We will read multiple disc blocks up to MAXBSIZE bytes in a loop until we hit a chunk which
			// is less than one block size. That will be read in the third part.
			
			while ( ( residual > kPhysicalMediaBlockSize ) &&
					( offset < cddaNodePtr->u.file.nodeInfoPtr->numBytes ) )
			{
				
				UInt32		blocksToRead = 0;
				
				// Read in as close to MAXBSIZE chunks as possible
				if ( residual > kMaxBytesPerRead )
				{
					blocksToRead	= kMaxBlocksPerRead;
					count			= kMaxBytesPerRead;
				}
				
				else
				{
					blocksToRead	= residual / kPhysicalMediaBlockSize;
					count			= blocksToRead * kPhysicalMediaBlockSize;
				}
				
				// bread kMaxBlocksPerRead blocks and put them in the cache.
				error = bread ( cddaNodePtr->blockDeviceVNodePtr,
								blockNumber,
								count,
								NOCRED,
								&bufPtr );
				
				if ( error != 0 )
				{
					
					brelse ( bufPtr );
					return ( error );
					
				}
				
				count = ulmin ( count, cddaNodePtr->u.file.nodeInfoPtr->numBytes - offset );
				
				// Copy the data
				bcopy ( bufPtr->b_data, ( void * ) vmOffsetPtr + offset + headerSize, count );
				
				// Increment/decrement counters
				offset		+= count;
				residual	-= count;
				
				// Make sure we mark any intermediate buffers as invalid as we don't need
				// to keep them.
				SET ( bufPtr->b_flags, B_INVAL );
				
				// Release this buffer back into the buffer pool. 
				brelse ( bufPtr );
				
				// Update offset
				blockNumber += blocksToRead;
				
			}
			
			// Part 3
			// Now that we have read everything, we read the tail end which is a partial sector.
			// Sometimes we don't need to execute this step since there isn't a tail.
			if ( ( residual > 0  ) &&
				 ( offset < cddaNodePtr->u.file.nodeInfoPtr->numBytes ) )
			{
				
				count = ulmin ( residual, cddaNodePtr->u.file.nodeInfoPtr->numBytes - offset );
				
				// Read the one sector
				error = bread ( cddaNodePtr->blockDeviceVNodePtr,
								blockNumber,
								kPhysicalMediaBlockSize,
								NOCRED,
								&bufPtr );
				
				if ( error != 0 )
				{
					
					brelse ( bufPtr );
					return ( error );
					
				}
				
				// Copy the data
				bcopy ( bufPtr->b_data, ( void * ) vmOffsetPtr + offset + headerSize, count );
				
				// Increment/decrement counters
				offset		+= count;
				residual	-= count;
				
				// Release this buffer back into the buffer pool. 
				brelse ( bufPtr );
				
			}
			
		}
		
		// Unmap the physical page from the kernel address space
		kret = ubc_upl_unmap ( pageInArgsPtr->a_pl );
		
		// If we got an error, panic for now
		if ( kret != KERN_SUCCESS )
		{
			
			panic ( "CDDA_PageIn: error unmapping buffer from kernel space!" );
			
		}
		
		if ( !nocommit )
		{
			
			// Commit the page to the vm subsystem
			ubc_upl_commit_range ( 	pageInArgsPtr->a_pl,
									pageInArgsPtr->a_pl_offset,
									pageInArgsPtr->a_size,
									UPL_COMMIT_FREE_ON_EMPTY | UPL_COMMIT_CLEAR_DIRTY );
			
		}
		
	}
	
	DebugLog ( ( "CDDA_PageIn: exiting...\n" ) );
	
	return ( error );
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_GetAttributes - This routine gets the attributes for a folder/file
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_GetAttributes ( struct vop_getattr_args * getAttrArgsPtr )
/*
struct vop_getattr_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	struct vattr *a_vap;
	struct ucred *a_cred;
	struct proc *a_p;
};
*/
{
	
	struct vnode *			vNodePtr 		= NULL;
	struct vattr *			attributesPtr 	= NULL;
	AppleCDDANodePtr		cddaNodePtr		= NULL;
	AppleCDDAMountPtr		cddaMountPtr	= NULL;
	
	DebugLog ( ( "CDDA_GetAttributes: Entering.\n" ) );
	
	DebugAssert ( ( getAttrArgsPtr != NULL ) );
	
	vNodePtr 		= getAttrArgsPtr->a_vp;
	attributesPtr 	= getAttrArgsPtr->a_vap;
	
	DebugAssert ( ( vNodePtr != NULL ) );
	DebugAssert ( ( attributesPtr != NULL ) );
	
	cddaMountPtr	= VFSTOCDDA ( vNodePtr->v_mount );
	cddaNodePtr		= VTOCDDA ( vNodePtr );

	DebugAssert ( ( cddaNodePtr != NULL ) );
	DebugAssert ( ( cddaMountPtr != NULL ) );

	DebugLog ( ( "nodeID = %ld.\n", cddaNodePtr->nodeID ) );
	
	// Set filesystem ID since we called vfs_getnewfsid() on mount
	attributesPtr->va_fsid		= vNodePtr->v_mount->mnt_stat.f_fsid.val[0];
	
	attributesPtr->va_fileid 	= cddaNodePtr->nodeID;		// Set the nodeID
	attributesPtr->va_type		= vNodePtr->v_type;			// Set the VNode type (e.g. VREG, VDIR)
	attributesPtr->va_blocksize = kPhysicalMediaBlockSize;	// Set preferred block size for I/O requests
	
	// Set all the time fields
	attributesPtr->va_atime		= cddaMountPtr->mountTime;	// Last accessed time
	attributesPtr->va_mtime 	= cddaMountPtr->mountTime;	// Last modification time
	attributesPtr->va_ctime 	= cddaMountPtr->mountTime;	// Last change time
	
	// These fields are the same
	attributesPtr->va_uid 		= kUnknownUserID;	// "unknown"
	attributesPtr->va_gid 		= kUnknownGroupID;	// "unknown"
	attributesPtr->va_gen 		= 0;
	attributesPtr->va_flags 	= 0;
	attributesPtr->va_rdev 		= NULL;
	
	// Set some common mode flags
	attributesPtr->va_mode		= S_IRUSR | S_IRGRP | S_IROTH;	// Read is ok for user, group, other
	
	// If it's the root, set some flags for it.
	if ( vNodePtr->v_flag & VROOT )
	{
		
		attributesPtr->va_mode 		|= S_IFDIR;									// It's a directory
		attributesPtr->va_mode		|= S_IXUSR | S_IXGRP | S_IXOTH;				// Execute is ok for user, group, other
		attributesPtr->va_nlink 	= kAppleCDDANumberOfRootDirReferences;		// Number of file refs: "." and ".."
		
		// Number of Tracks + ".", "..", and ".TOC.plist"
		attributesPtr->va_bytes	= attributesPtr->va_size = ( cddaNodePtr->u.directory.entryCount ) * sizeof ( struct dirent );
		
	}

	// If it isn't the root vnode, it's a file
	else
	{
		
		attributesPtr->va_mode 		|= S_IFREG;	// It's a file...
		attributesPtr->va_nlink 	= kAppleCDDANumberOfFileReferences;	// Just the file itself
		
		// Is it a track?
		if ( cddaNodePtr->nodeType == kAppleCDDATrackType )
		{
			
			// Set file size in bytes
			attributesPtr->va_bytes	= attributesPtr->va_size = cddaNodePtr->u.file.nodeInfoPtr->numBytes;
			
		}
		
		// Is it the ".TOC.plist" file?
		else if ( cddaNodePtr->nodeType == kAppleCDDAXMLFileType )
		{
			
			// Set file size in bytes
			attributesPtr->va_bytes	= attributesPtr->va_size = cddaNodePtr->u.xmlFile.fileSize;
			
		}
		
	}
	
	DebugLog ( ( "CDDA_GetAttributes: exiting...\n" ) );
	
	return ( 0 );
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_GetAttributesList - 	This routine gets the attribute list for the
//								specified vnode.
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_GetAttributesList ( struct vop_getattrlist_args * attrListArgsPtr )
/*
struct vop_getattrlist_args {
	struct vnode *a_vp;
	struct attrlist *a_alist
	struct uio *a_uio;
	struct ucred *a_cred;
	struct proc *a_p;
};
*/
{
	
	struct vnode *		vNodePtr 			= NULL;
	AppleCDDANodePtr	cddaNodePtr			= NULL;
	struct attrlist *	attributesListPtr	= NULL;
	int 				error = 0;
	UInt32 				fixedblocksize;
	UInt32 				attrblocksize;
	UInt32 				attrbufsize;
	void *				attrbufptr;
	void *				attrptr;
	void *				varptr;
	
	DebugLog ( ( "CDDA_GetAttributesList: Entering.\n" ) );
	
	DebugAssert ( ( attrListArgsPtr != NULL ) );
	
	vNodePtr 			= attrListArgsPtr->a_vp;
	attributesListPtr 	= attrListArgsPtr->a_alist;
	
	DebugAssert ( ( vNodePtr != NULL ) );
	DebugAssert ( ( attributesListPtr != NULL ) );
	
	cddaNodePtr	= VTOCDDA ( vNodePtr );
	
	DebugAssert ( ( cddaNodePtr != NULL ) );
	
	DebugAssert ( ( ap->a_uio->uio_rw == UIO_READ ) );
	
	// Make sure the caller isn't asking for an attribute we don't support.
	if ( ( attributesListPtr->bitmapcount != 5 ) ||
		 ( ( attributesListPtr->commonattr & ~kAppleCDDACommonAttributesValidMask ) != 0 ) ||
		 ( ( attributesListPtr->volattr & ~kAppleCDDAVolumeAttributesValidMask ) != 0 ) ||
		 ( ( attributesListPtr->dirattr & ~kAppleCDDADirectoryAttributesValidMask ) != 0 ) ||
		 ( ( attributesListPtr->fileattr & ~kAppleCDDAFileAttributesValidMask ) != 0 ) ||
		 ( ( attributesListPtr->forkattr & ~kAppleCDDAForkAttributesValidMask ) != 0) )
	{
		
		DebugLog ( ( "CDDA_GetAttributesList: bad attrlist\n" ) );
		return EOPNOTSUPP;
		
	}
	
	// Requesting volume information requires setting the ATTR_VOL_INFO bit
	// and volume info requests are mutually exclusive with all other info requests:
	if ( ( attributesListPtr->volattr != 0 ) && ( ( ( attributesListPtr->volattr & ATTR_VOL_INFO ) == 0 ) ||
		 ( attributesListPtr->dirattr != 0 ) || ( attributesListPtr->fileattr != 0 ) || ( attributesListPtr->forkattr != 0 ) ) )
	{
		
		DebugLog ( ( "CDDA_GetAttributesList: conflicting information requested\n" ) );
		return EINVAL;
		
	}
	
	fixedblocksize = CalculateAttributeBlockSize ( attributesListPtr );
	
	// UInt32 for length longword
	attrblocksize = fixedblocksize + ( sizeof ( UInt32 ) );
	
	if ( attributesListPtr->commonattr & ATTR_CMN_NAME )
		attrblocksize += kCDDAMaxFileNameBytes + 1;
		
	if ( attributesListPtr->volattr & ATTR_VOL_MOUNTPOINT )
		attrblocksize += PATH_MAX;
		
	if ( attributesListPtr->volattr & ATTR_VOL_NAME )
		attrblocksize += kCDDAMaxFileNameBytes + 1;
	
	attrbufsize = MIN ( attrListArgsPtr->a_uio->uio_resid, attrblocksize );
	
	DebugLog ( ( "CDDA_GetAttributesList: 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;
	
	// Set buffer length in case of errors
	*( ( UInt32 * ) attrptr ) = 0;
	
	// Reserve space for length field
	++( ( UInt32 * ) attrptr );
	
	// Point to variable-length storage
	varptr = ( ( char * ) attrptr ) + fixedblocksize;
	
	DebugLog ( ( "CDDA_GetAttributesList: attrptr = 0x%08X, varptr = 0x%08X...\n", ( u_int ) attrptr, ( u_int ) varptr ) );
	
	PackAttributesBlock ( attributesListPtr, vNodePtr, &attrptr, &varptr );

	DebugLog ( ( "CDDA_GetAttributesList: calculating MIN\n" ) );
	
	// Don't copy out more data than was generated
	attrbufsize = MIN ( attrbufsize, ( u_int ) varptr - ( u_int ) attrbufptr );
	
	DebugLog ( ( "CDDA_GetAttributesList: setting attrbufsize = %ld\n", attrbufsize ) );
	
	// Set actual buffer length for return to caller
	*( ( UInt32 * ) attrbufptr ) = attrbufsize;
	
	DebugLog ( ( "CDDA_GetAttributesList: copying %ld bytes to user address 0x%08X.\n",
				 ( UInt32 ) attrbufsize, ( u_int ) attrListArgsPtr->a_uio->uio_iov->iov_base ) );
	
	error = uiomove ( ( caddr_t ) attrbufptr, attrbufsize, attrListArgsPtr->a_uio );
	if ( error != 0 )
	{
		DebugLog ( ( "CDDA_GetAttributesList: error %d on uiomove.\n", error ) );
	}
	
	DebugLog ( ( "CDDA_GetAttributesList: calling FREE\n" ) );
	FREE ( attrbufptr, M_TEMP );
	
	return error;
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Inactive - This routine simply unlocks a vnode.
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Inactive ( struct vop_inactive_args * inactiveArgsPtr )
/*
struct vop_inactive_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	struct proc *a_p;
};
*/
{
	
	DebugLog ( ( "CDDA_Inactive: Entering.\n" ) );
	
	DebugAssert ( ( inactiveArgsPtr != NULL ) );
	
	// We don't do anything special, so call VFS function to handle it
	( void ) nop_inactive ( inactiveArgsPtr );
		
	DebugLog ( ( "CDDA_Inactive: exiting...\n" ) );
	
	return ( 0 );
	
}


#if RENAME_SUPPORTED
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Rename - This routine renames a file in the filesystem.
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Rename ( struct vop_rename_args * renameArgsPtr )
/*
struct vop_rename_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_fdvp;
	struct vnode *a_fvp;
	struct componentname *a_fcnp;
	struct vnode *a_tdvp;
	struct vnode *a_tvp;
	struct componentname *a_tcnp;
};
*/
{

	struct vnode *			targetVNodePtr 			= NULL;
	struct vnode *			targetParentVNodePtr 	= NULL;
	struct vnode *			sourceVNodePtr 			= NULL;
	struct vnode *			sourceParentVNodePtr 	= NULL;
	struct componentname *	targetComponentNamePtr 	= NULL;
	struct componentname *	sourceComponentNamePtr 	= NULL;
	struct proc	*			procPtr					= NULL;
	AppleCDDAMountPtr		cddaMountPtr			= NULL;
	AppleCDDANodePtr		sourceCDDANodePtr		= NULL;
	AppleCDDANodePtr		sourceParentCDDANodePtr	= NULL;
	AppleCDDANodeInfoPtr	nodeInfoPtr				= NULL;
	int						retval					= 0;
	struct timeval			tv;
	struct timespec			timespec;
	
	DebugLog ( ( "CDDA_Rename: Entering.\n" ) );	
	DebugAssert ( ( renameArgsPtr != NULL ) );
	
	targetVNodePtr 			= renameArgsPtr->a_tvp;
	targetParentVNodePtr 	= renameArgsPtr->a_tdvp;
	sourceVNodePtr 			= renameArgsPtr->a_fvp;
	sourceParentVNodePtr 	= renameArgsPtr->a_fdvp;
	targetComponentNamePtr 	= renameArgsPtr->a_tcnp;
	sourceComponentNamePtr 	= renameArgsPtr->a_fcnp;
	
	DebugAssert ( ( targetVNodePtr != NULL ) );
	DebugAssert ( ( targetParentVNodePtr != NULL ) );
	DebugAssert ( ( sourceVNodePtr != NULL ) );
	DebugAssert ( ( sourceParentVNodePtr != NULL ) );
	DebugAssert ( ( targetComponentNamePtr != NULL ) );
	DebugAssert ( ( sourceComponentNamePtr != NULL ) );
	
	procPtr = sourceComponentNamePtr->cn_proc;
	
	DebugAssert ( ( procPtr != NULL ) );
	
    if ( ( ( targetComponentNamePtr->cn_flags & HASBUF ) == 0 ) ||
		 ( ( sourceComponentNamePtr->cn_flags & HASBUF ) == 0 ) )
        panic ( "CDDA_Rename: no name" );
	
	// If targetVNodePtr is not NULL, we bail.
	// We only support in place renaming.
	if ( targetVNodePtr != NULL )
	{
		
		DebugLog ( ( "CDDA_Rename: sourceVNodePtr != targetVNodePtr.\n" ) );
		retval = EINVAL;
		goto Exit;
		
	}
		
	sourceCDDANodePtr 		= VTOCDDA ( sourceVNodePtr );
	sourceParentCDDANodePtr	= VTOCDDA ( sourceParentVNodePtr );
	
	// 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 ( sourceCDDANodePtr->nodeType == kAppleCDDADirectoryType )
	{
		
		DebugLog ( ( "CDDA_Rename: sourceCDDANodePtr->nodeType == kAppleCDDADirectoryType.\n" ) );
		retval = EINVAL;
		goto Exit;
		
	}
	
	if ( sourceCDDANodePtr->nodeType == kAppleCDDAXMLFileType )
	{
		
		DebugLog ( ( "CDDA_Rename: sourceCDDANodePtr->nodeType == kAppleCDDAXMLFileType.\n" ) );
		retval = EPERM;
		goto Exit;
		
	}
	
	// Verify the name makes sense (has .aiff at the end and
	// has the track number at the beginning)
	if ( targetComponentNamePtr->cn_nameptr )
	{
		
		char		buffer[3];
		char *		ptr;
		char *		ptr2;
		UInt32		size;
		UInt8		value;
		
		ptr = strchr ( targetComponentNamePtr->cn_nameptr, ' ' );
		if ( ptr == NULL )
		{
			
			DebugLog ( ( "CDDA_Rename: Need space between number and name.\n" ) );
			retval = EINVAL;
			goto Exit;
			
		}
		
		size = ( UInt32 ) ptr - ( UInt32 ) targetComponentNamePtr->cn_nameptr;
		if ( size < 1 )
		{
			
			DebugLog ( ( "CDDA_Rename: Amount of data to copy is nothing, bail\n" ) );
			retval = EINVAL;
			goto Exit;
			
		}
			
		strncpy ( buffer, targetComponentNamePtr->cn_nameptr, size );
		buffer[size] = 0;
		value = atoi ( buffer );
		
		if ( value != sourceCDDANodePtr->u.file.nodeInfoPtr->trackDescriptor.point )
		{
			
			DebugLog ( ( "CDDA_Rename: Name is invalid\n" ) );
			retval = EINVAL;
			goto Exit;
			
		}
		
		// Now check for .aiff
		ptr = strchr ( targetComponentNamePtr->cn_nameptr, '.' );
		if ( ptr == NULL )
		{
			
			DebugLog ( ( "CDDA_Rename: No .aiff at the end\n" ) );
			retval = EINVAL;
			goto Exit;
			
		}
		
		// make sure we go to the last . in the name (so tedious)...
		ptr2 = strchr ( ptr + 1, '.' );		
		while ( ptr2 != NULL )
		{
			
			DebugLog ( ( "CDDA_Rename: In while loop\n" ) );
			
			ptr = ptr2;
			ptr2 = strchr ( ptr2 + 1, '.' );
			
		}
		
		size = ( UInt32 ) ptr - ( UInt32 ) targetComponentNamePtr->cn_nameptr + strlen ( ".aiff" );
		if ( size != strlen ( targetComponentNamePtr->cn_nameptr ) )
		{
			
			DebugLog ( ( "CDDA_Rename: .aiff is not at the ending\n" ) );
			retval = EINVAL;
			goto Exit;
			
		}
		
	}
	
	// remove the existing entry from the namei cache:
	cache_purge ( sourceVNodePtr );
	DebugLog ( ( "CDDA_Rename: finished cach_purge.\n" ) );
	
	// Change the name in the NodeInfoArray
	nodeInfoPtr = sourceCDDANodePtr->u.file.nodeInfoPtr;
	FREE ( nodeInfoPtr->name, M_TEMP );
	DebugLog ( ( "CDDA_Rename: FREE'd name.\n" ) );
	
	MALLOC ( nodeInfoPtr->name, char *, strlen ( targetComponentNamePtr->cn_nameptr ) + 1, M_TEMP, M_WAITOK );
	strcpy ( nodeInfoPtr->name, targetComponentNamePtr->cn_nameptr );
	nodeInfoPtr->nameSize = strlen ( targetComponentNamePtr->cn_nameptr );
	DebugLog ( ( "CDDA_Rename: finished strcpy of name.\n" ) );
	
	// Timestamp the parent directory.
	tv = time;
	
	TIMEVAL_TO_TIMESPEC ( &tv, &timespec );
	cddaMountPtr = VFSTOCDDA ( sourceVNodePtr->v_mount );
	cddaMountPtr->mountTime	= timespec;
	
	
Exit:
	
	
	if ( targetVNodePtr != NULL )
	{
		
		vput ( targetVNodePtr );
		DebugLog ( ( "CDDA_Rename: vput of targetVNodePtr done.\n" ) );
		
	}
	
	vput ( targetParentVNodePtr );
	DebugLog ( ( "CDDA_Rename: vput of targetParentVNodePtr done.\n" ) );
	
	vrele ( sourceParentVNodePtr );
	DebugLog ( ( "CDDA_Rename: vrele of sourceParentVNodePtr done.\n" ) );
	
	vrele ( sourceVNodePtr );
	DebugLog ( ( "CDDA_Rename: vrele of sourceVNodePtr done.\n" ) );
	
	return retval;
	
}
#endif	/* RENAME_SUPPORTED */

//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Remove - 	This routine removes a file from the name space. Since we
//					are a read-only volume, we release any locks if appropriate
//					and return EROFS
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int 
CDDA_Remove ( struct vop_remove_args * removeArgsPtr )
/*
struct vop_remove_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_dvp;
	struct vnode *a_vp;
	struct componentname *a_cnp;
};
*/
{
		
	DebugLog ( ( "CDDA_Remove: Entering.\n" ) );

	DebugAssert ( ( removeArgsPtr != NULL ) );
	
	// We don't do anything special, so call VFS function to handle it
	( void ) nop_remove ( removeArgsPtr );
		
	DebugLog ( ( "CDDA_Remove: exiting...\n" ) );
	
	// Return the read-only filesystem error
	return ( EROFS );
	
}   


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_RmDir - 	This routine removes a directory from the name space.
//					Since we are a read-only volume, we release any locks
//					and return EROFS
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int 
CDDA_RmDir ( struct vop_rmdir_args * removeDirArgsPtr )
/*
struct vop_rmdir_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_dvp;
	struct vnode *a_vp;
	struct componentname *a_cnp;
};
*/
{

	DebugLog ( ( "CDDA_RmDir: Entering.\n" ) );

	DebugAssert ( ( removeDirArgsPtr != NULL ) );

	// Call nop_rmdir to release locks
	( void ) nop_rmdir ( removeDirArgsPtr );
	
	DebugLog ( ( "CDDA_RmDir: exiting...\n" ) );
	
	// Return the read-only filesystem error
	return ( EROFS );
	
}   


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Reclaim - This routine reclaims a vnode for use by the system.
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Reclaim ( struct vop_reclaim_args * reclaimArgsPtr )
/*
struct vop_reclaim_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	struct proc *a_p;
};
*/
{
	
	int		error 	= 0;
	
	DebugLog ( ( "CDDA_Reclaim: Entering.\n" ) );

	DebugAssert ( ( reclaimArgsPtr != NULL ) );

	error = DisposeCDDANode ( reclaimArgsPtr->a_vp, reclaimArgsPtr->a_p );	

	DebugLog ( ( "CDDA_Reclaim: exiting...\n" ) );
	
	return ( error );

}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Access - This routine checks the access attributes for a node
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Access ( struct vop_access_args * accessArgsPtr )
/*
struct vop_access_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	int a_mode;
	struct ucred *a_cred;
	struct proc *a_p;
};
*/
{
	
	AppleCDDANodePtr	cddaNodePtr		= NULL;
	struct ucred * 		cred			= NULL;
	mode_t				mode			= 0;
	int					error			= 0;
	
	DebugLog ( ( "CDDA_Access: Entering.\n" ) );
	
	DebugAssert ( ( accessArgsPtr != NULL ) );
	
	cred 		= accessArgsPtr->a_cred;
	mode 		= accessArgsPtr->a_mode;	
	cddaNodePtr = VTOCDDA ( accessArgsPtr->a_vp );
	
	DebugAssert ( ( cddaNodePtr != NULL ) );
	
	// Disallow writes on read-only filesystem
	if ( mode & VWRITE )
	{
		
		if ( accessArgsPtr->a_vp->v_mount->mnt_flag & MNT_RDONLY )
		{
			
			// They tried to write to a read-only mountpoint, return EROFS
			return ( EROFS );
		
		}
		
		else
		{

			// Should never get here
			DebugLog ( ( "Error: this filesystem is read-only, but isn't marked read-only" ) );
			panic ( "Read-Only filesystem" );
		
		}
		
	}
	
	// user id 0 (super-user) always gets access
	if ( cred->cr_uid == 0 )
	{

		error = 0;

	}
	
	// everyone else gets access too!
	error = 0;
	
	
	DebugLog ( ( "CDDA_Access: exiting...\n" ) );
	
	return ( error );

}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_BlockToOffset - 	This routine converts logical block number to file
//							offset
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_BlockToOffset ( struct vop_blktooff_args * blockToOffsetArgsPtr )
/*
struct vop_blktooff_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	daddr_t a_lblkno;
	off_t *a_offset;
};
*/
{	

	DebugLog ( ( "CDDA_BlockToOffset: Entering.\n" ) );

	DebugAssert ( ( blockToOffsetArgsPtr != NULL ) );
	
	if ( blockToOffsetArgsPtr->a_vp == NULL )
	{

		DebugLog ( ( "CDDA_BlockToOffset: incoming vnode is NULL.\n" ) );
		return ( EINVAL );

	}
	
	*( blockToOffsetArgsPtr->a_offset ) =
			( off_t ) ( blockToOffsetArgsPtr->a_lblkno * PAGE_SIZE );
	
	DebugLog ( ( "CDDA_BlockToOffset: exiting...\n" ) );
	
	return ( 0 );
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_OffsetToBlock - 	This routine converts a file offset to a logical
//							block number
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_OffsetToBlock ( struct vop_offtoblk_args * offsetToBlockArgsPtr )
/*
struct vop_offtoblk_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	off_t a_offset;
	daddr_t *a_lblkno;
};
*/
{

	DebugLog ( ( "CDDA_OffsetToBlock: Entering.\n" ) );

	DebugAssert ( ( offsetToBlockArgsPtr != NULL ) );

	if ( offsetToBlockArgsPtr->a_vp == NULL )
	{

		DebugLog ( ( "CDDA_OffsetToBlock: incoming vnode is NULL.\n" ) );
		return ( EINVAL );

	}
	
	*( offsetToBlockArgsPtr->a_lblkno ) = ( offsetToBlockArgsPtr->a_offset / PAGE_SIZE );

	DebugLog ( ( "CDDA_OffsetToBlock: exiting...\n" ) );

	return ( 0 );
	
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Pathconf - Return POSIX pathconf information applicable to
//					special devices
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Pathconf ( struct vop_pathconf_args * pathConfArgsPtr )
/*
struct vop_pathconf_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	int a_name;
	register_t *a_retval;
};
*/
{
	
	int returnValue	= 0;
	
	DebugLog ( ( "CDDA_Pathconf: Entering.\n" ) );

	DebugAssert ( ( pathConfArgsPtr != NULL ) );

	switch ( pathConfArgsPtr->a_name )
	{
		
		case _PC_LINK_MAX:
			*pathConfArgsPtr->a_retval = 1;
			break;
			
		case _PC_NAME_MAX:
			*pathConfArgsPtr->a_retval = NAME_MAX;
			break;
		
		case _PC_PATH_MAX:
			*pathConfArgsPtr->a_retval = PATH_MAX;
			break;
					
		case _PC_CHOWN_RESTRICTED:
			*pathConfArgsPtr->a_retval = 1;
			break;

		case _PC_NO_TRUNC:
			*pathConfArgsPtr->a_retval = 0;
			break;
			
		default:
			returnValue = EINVAL;
			break;
			
	}

	DebugLog ( ( "CDDA_Pathconf: exiting with returnValue = %d.\n", returnValue ) );
	
	return ( returnValue );
		
}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Lock -  Locks a vnode
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Lock ( struct vop_lock_args * lockArgsPtr )
/*
struct vop_lock_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	int a_flags;
	struct proc *a_p;
};
*/
{

	struct vnode * 		vNodePtr 	= NULL;
	AppleCDDANodePtr	cddaNodePtr = NULL;
	int					error		= 0;
		
	DebugLog ( ( "CDDA_Lock: Entering.\n" ) );
	
	DebugAssert ( ( lockArgsPtr != NULL ) );

	vNodePtr = lockArgsPtr->a_vp;
	DebugAssert ( ( vNodePtr != NULL ) );
	
	cddaNodePtr = VTOCDDA ( vNodePtr );
	DebugAssert ( ( cddaNodePtr != NULL ) );
	
	// Pass it along to the lockmgr to do the locking
	error = lockmgr ( &cddaNodePtr->lock, lockArgsPtr->a_flags,
					   &vNodePtr->v_interlock, lockArgsPtr->a_p );
	
	if ( error != 0 )
	{
		
		if ( ( lockArgsPtr->a_flags & LK_NOWAIT ) == 0 )
		{
			
			DebugLog ( ( "CDDA_Lock: error %d trying to lock vnode (flags = 0x%08X).\n", error, lockArgsPtr->a_flags ) );
			
		}
		
	}

	DebugLog ( ( "CDDA_Lock: exiting...\n" ) );
	
	return ( error );

}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Unlock -  Unlocks a vnode
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Unlock ( struct vop_unlock_args * unlockArgsPtr )
/*
struct vop_unlock_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
	int a_flags;
	struct proc *a_p;
};
*/
{

	struct vnode * 		vNodePtr 	= NULL;
	AppleCDDANodePtr	cddaNodePtr = NULL;
	int					error		= 0;
	
	DebugLog ( ( "CDDA_Unlock: Entering.\n" ) );
	
	DebugAssert ( ( unlockArgsPtr != NULL ) );

	vNodePtr = unlockArgsPtr->a_vp;
	DebugAssert ( ( vNodePtr != NULL ) );
	
	cddaNodePtr = VTOCDDA ( vNodePtr );
	DebugAssert ( ( cddaNodePtr != NULL ) );
	
	// Pass it along to the lockmgr to do the unlocking
	error = lockmgr ( &cddaNodePtr->lock, unlockArgsPtr->a_flags | LK_RELEASE,
					   &vNodePtr->v_interlock, unlockArgsPtr->a_p );
	
	if ( error != 0 )
	{
		
		DebugLog ( ( "CDDA_Unlock: error %d trying to unlock vnode (flags = 0x%08X).\n", error, unlockArgsPtr->a_flags ) );
		
	}

	DebugLog ( ( "CDDA_Unlock: exiting...\n" ) );
	
	return ( error );

}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_IsLocked -  Gets lock status on a vnode
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_IsLocked ( struct vop_islocked_args * isLockedArgsPtr )
/*
struct vop_islocked_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
};
*/
{
	
	struct vnode * 		vNodePtr 	= NULL;
	AppleCDDANodePtr	cddaNodePtr = NULL;
	int					lockStatus	= 0;

	DebugLog ( ( "CDDA_IsLocked: Entering.\n" ) );
		
	DebugAssert ( ( isLockedArgsPtr != NULL ) );

	vNodePtr = isLockedArgsPtr->a_vp;
	DebugAssert ( ( vNodePtr != NULL ) );
	
	cddaNodePtr = VTOCDDA ( vNodePtr );
	DebugAssert ( ( cddaNodePtr != NULL ) );
	
	// Pass along to the lockmgr to get the lock status
	lockStatus = lockstatus ( &cddaNodePtr->lock );
	
	DebugLog ( ( "CDDA_IsLocked: exiting...\n" ) );
	
	return ( lockStatus );

}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_Print -  Print out the contents of a CDDA vnode.
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_Print ( struct vop_print_args * printArgsPtr )
/*
struct vop_print_args {
	struct vnodeop_desc *a_desc;
	struct vnode *a_vp;
};
*/
{

#if DEBUG
	DebugAssert ( ( printArgsPtr != NULL ) );
#else
	#pragma unused ( printArgsPtr )
#endif

	DebugLog ( ( "CDDA_Print: Entering.\n" ) );
	DebugLog ( ( "CDDA_Print: exiting...\n" ) );
	
	return ( 0 );

}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	CDDA_VFree -  Does nothing.
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int
CDDA_VFree ( struct vop_vfree_args * vFreeArgsPtr )
{

#if DEBUG
	DebugAssert ( ( vFreeArgsPtr != NULL ) );
#else
	#pragma unused ( vFreeArgsPtr )
#endif

	DebugLog ( ( "CDDA_VFree: Entering.\n" ) );
	DebugLog ( ( "CDDA_VFree: exiting...\n" ) );
	
	return ( 0 );

}


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	Other macro'd function definitions
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

int ( **gCDDA_VNodeOp_p )( void * );
#define VOPFUNC int ( * )( void * )


#if 0
#pragma mark -
#endif


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//	VNode Operation Vector Entry Description (Dispatch Table)
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ

struct vnodeopv_entry_desc gCDDA_VNodeOperationEntries[] =
{
	{ &vop_default_desc, 		( VOPFUNC ) vn_default_error },
	{ &vop_lookup_desc, 		( VOPFUNC ) CDDA_Lookup },				// lookup
	{ &vop_create_desc, 		( VOPFUNC ) err_create },				// create
	{ &vop_mknod_desc, 			( VOPFUNC ) err_mknod },				// mknod
	{ &vop_open_desc, 			( VOPFUNC ) CDDA_Open },				// open
	{ &vop_close_desc, 			( VOPFUNC ) CDDA_Close },				// close
	{ &vop_access_desc, 		( VOPFUNC ) CDDA_Access },				// access
	{ &vop_getattr_desc, 		( VOPFUNC ) CDDA_GetAttributes },		// getattr
	{ &vop_setattr_desc, 		( VOPFUNC ) nop_setattr },				// setattr
	{ &vop_read_desc, 			( VOPFUNC ) CDDA_Read },				// read
	{ &vop_write_desc, 			( VOPFUNC ) err_write },				// write
	{ &vop_ioctl_desc, 			( VOPFUNC ) err_ioctl },				// ioctl
	{ &vop_select_desc, 		( VOPFUNC ) err_select },				// select
	{ &vop_exchange_desc, 		( VOPFUNC ) err_exchange },				// exchange
	{ &vop_mmap_desc, 			( VOPFUNC ) err_mmap },					// mmap
	{ &vop_fsync_desc, 			( VOPFUNC ) nop_fsync },				// fsync
	{ &vop_seek_desc, 			( VOPFUNC ) err_seek },					// seek
	{ &vop_remove_desc, 		( VOPFUNC ) CDDA_Remove },				// remove
	{ &vop_link_desc, 			( VOPFUNC ) err_link },					// link
#if RENAME_SUPPORTED
	{ &vop_rename_desc, 		( VOPFUNC ) CDDA_Rename },				// rename
#else
	{ &vop_rename_desc, 		( VOPFUNC ) err_rename },				// rename	
#endif /* RENAME_SUPPORTED */
	{ &vop_mkdir_desc, 			( VOPFUNC ) err_mkdir },				// mkdir
	{ &vop_rmdir_desc, 			( VOPFUNC ) CDDA_RmDir },				// rmdir
	{ &vop_getattrlist_desc, 	( VOPFUNC ) CDDA_GetAttributesList },	// getattrlist
	{ &vop_setattrlist_desc, 	( VOPFUNC ) err_setattrlist },			// setattrlist
	{ &vop_symlink_desc, 		( VOPFUNC ) err_symlink },				// symlink
	{ &vop_readdir_desc, 		( VOPFUNC ) CDDA_ReadDir },				// readdir
	{ &vop_readdirattr_desc, 	( VOPFUNC ) err_readdirattr },			// readdirattr
	{ &vop_readlink_desc, 		( VOPFUNC ) err_readlink },				// readlink
	{ &vop_abortop_desc, 		( VOPFUNC ) err_abortop },				// abortop
	{ &vop_inactive_desc, 		( VOPFUNC ) CDDA_Inactive },			// inactive
	{ &vop_reclaim_desc, 		( VOPFUNC ) CDDA_Reclaim },				// reclaim
	{ &vop_lock_desc, 			( VOPFUNC ) CDDA_Lock },				// lock
	{ &vop_unlock_desc, 		( VOPFUNC ) CDDA_Unlock },				// unlock
	{ &vop_bmap_desc, 			( VOPFUNC ) err_bmap },					// bmap
	{ &vop_strategy_desc, 		( VOPFUNC ) err_strategy },				// strategy
	{ &vop_print_desc, 			( VOPFUNC ) CDDA_Print },				// print
	{ &vop_islocked_desc, 		( VOPFUNC ) CDDA_IsLocked },			// islocked
	{ &vop_pathconf_desc, 		( VOPFUNC ) CDDA_Pathconf },			// pathconf
	{ &vop_advlock_desc, 		( VOPFUNC ) err_advlock },				// advlock
	{ &vop_reallocblks_desc,	( VOPFUNC ) err_reallocblks },			// reallocblks
	{ &vop_truncate_desc, 		( VOPFUNC ) err_truncate },				// truncate
	{ &vop_allocate_desc, 		( VOPFUNC ) err_allocate },				// allocate
	{ &vop_update_desc, 		( VOPFUNC ) nop_update },				// update
	{ &vop_bwrite_desc, 		( VOPFUNC ) err_bwrite },				// bwrite
	{ &vop_pagein_desc, 		( VOPFUNC ) CDDA_PageIn },				// pagein
	{ &vop_pageout_desc, 		( VOPFUNC ) err_pageout },				// pageout
	{ &vop_blktooff_desc, 		( VOPFUNC ) CDDA_BlockToOffset },		// blktoff
	{ &vop_offtoblk_desc,		( VOPFUNC ) CDDA_OffsetToBlock },		// offtoblk
	{ &vop_cmap_desc,			( VOPFUNC ) err_cmap },					// cmap
	{ NULL, 					( VOPFUNC ) NULL }
};


struct vnodeopv_desc gCDDA_VNodeOperationsDesc =
{
	&gCDDA_VNodeOp_p,
	gCDDA_VNodeOperationEntries
};


//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ
//				End				Of			File
//ΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡΡ