/*

Copyright (c) 1992  Microsoft Corporation

Module Name:

	idindex.c

Abstract:

	This module contains the id index manipulation routines.

Author:

	Jameel Hyder (microsoft!jameelh)


Revision History:
	25 Apr 1992	Initial Version
	24 Feb 1993 SueA	Fix AfpRenameDfEntry and AfpMoveDfEntry to invalidate
						the entire pathcache if the object of the move/rename
						is a directory that has children.  This is faster
						than having to either search the path cache for
						paths that have the moved/renamed dir path as a prefix,
						or having to walk down the subtree below that dir
						and invalidate the cache for each item there.
	05 Oct 1993 JameelH	Performance Changes. Merge cached afpinfo into the
						idindex structure. Make both the ANSI and the UNICODE
						names part of idindex. Added EnumCache for improving
						enumerate perf.
	05 Jun 1995 JameelH	Remove the ANSI name from DFE. Also keep the files
						in the directory in multiple hash buckets instead
						of a single one. The hash buckets are also
						seperated into files and directories for faster
						lookup. The notify filtering is now moved to completion
						time and made over-all optimizations related to iddb.

Notes:		Tab stop: 4

	Directories and files that the AFP server has enumerated have AFP ids
	associated with them. These ids are DWORD and start with 1 (0 is invalid).
	Id 1 is reserved for the 'parent of the volume root' directory.  Id 2 is
	reserved for the volume root directory.  Id 3 is reserved for the Network
	Trash directory.  Volumes that have no Network Trash will not use Id 3.

	These ids are per-volume and a database of ids are kept in memory in the
	form of a sibling tree which mirrors the part of the disk that the AFP
	server knows about (those files and dirs which have at some point been
	enumerated by a mac client).  An index is also maintained for this database
	which is in the form of a sorted hashed index.  The overflow hash links are
	sorted by AFP id in descending order.  This is based on the idea that the
	most recently created items will be accessed most frequently (at least
	for writable volumes).

--*/

#define IDINDEX_LOCALS
#define _IDDB_GLOBALS_
#define	FILENUM	FILE_IDINDEX

#include <afp.h>
#include <scavengr.h>
#include <fdparm.h>
#include <pathmap.h>
#include <afpinfo.h>
#include <access.h>	// for AfpWorldId

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, AfpDfeInit)
#pragma alloc_text(PAGE, AfpDfeDeInit)
#pragma alloc_text(PAGE, AfpFindDfEntryById)
#pragma alloc_text(PAGE, AfpFindEntryByUnicodeName)
#pragma alloc_text(PAGE, afpFindEntryByNtName)
#pragma alloc_text(PAGE, AfpAddDfEntry)
#pragma alloc_text(PAGE, AfpRenameDfEntry)
#pragma alloc_text(PAGE, AfpMoveDfEntry)
#pragma alloc_text(PAGE, AfpDeleteDfEntry)
#pragma alloc_text(PAGE, AfpExchangeIdEntries)
#pragma alloc_text(PAGE, AfpPruneIdDb)
#pragma alloc_text(PAGE, AfpEnumerate)
#pragma alloc_text(PAGE, AfpCatSearch)
#pragma alloc_text(PAGE, afpPackSearchParms)
#pragma alloc_text(PAGE, AfpSetDFFileFlags)
#pragma alloc_text(PAGE, AfpCacheParentModTime)
#pragma alloc_text(PAGE, afpAllocDfe)
#pragma alloc_text(PAGE, afpFreeDfe)
#pragma alloc_text(PAGE, AfpFreeIdIndexTables)
#pragma alloc_text(PAGE, AfpInitIdDb)
#pragma alloc_text(PAGE, afpSeedIdDb)
#pragma alloc_text(PAGE, afpDfeBlockAge)
#pragma alloc_text(PAGE, afpRenameInvalidWin32Name)
#ifdef	AGE_DFES
#pragma alloc_text( PAGE, AfpAgeDfEntries)
#endif
#if DBG
#pragma alloc_text( PAGE, afpDumpDfeTree)
#pragma alloc_text( PAGE, afpDisplayDfe)
#endif
#endif


/***	AfpDfeInit
 *
 *	Initialize the Swmr for Dfe Block package and start the aging scavenger for it.
 */
NTSTATUS
AfpDfeInit(
	VOID
)
{
	NTSTATUS	Status;

	// Initialize the DfeBlock Swmr
	AfpSwmrInitSwmr(&afpDfeBlockLock);

#if DBG
	AfpScavengerScheduleEvent(afpDumpDfeTree,
							  NULL,
							  2,
							  True);
#endif

	// Age out file and dir DFEs differently and seperately
	Status = AfpScavengerScheduleEvent(afpDfeBlockAge,
										afpDirDfeBlockHead,
										DIR_BLOCK_AGE_TIME,
										True);
	if (NT_SUCCESS(Status))
	{
		// Age out file and dir DFEs differently and seperately
		Status = AfpScavengerScheduleEvent(afpDfeBlockAge,
											afpFileDfeBlockHead,
											FILE_BLOCK_AGE_TIME,
											True);
	}
	return Status;
}


/***	AfpDfeDeInit
 *
 *	Free any Dfe Blocks that have not yet been aged out.
 */
VOID
AfpDfeDeInit(
	VOID
)
{
	PDFEBLOCK	pDfb;
	int			i;

	ASSERT (afpDfeAllocCount == 0);

	for (i = 0; i < MAX_BLOCK_TYPE; i++)
	{
		for (pDfb = afpDirDfeBlockHead[i];
			 pDfb != NULL;)
		{
			PDFEBLOCK	pFree;

			ASSERT(pDfb->dfb_NumFree == afpDfeNumDirBlocks[i]);
			pFree = pDfb;
			pDfb = pDfb->dfb_Next;
			AfpFreePAPagedMemory(pFree, BLOCK_ALLOC_SIZE);
#if	DBG
			afpDfbAllocCount --;
#endif
		}
		for (pDfb = afpFileDfeBlockHead[i];
			 pDfb != NULL;)
		{
			PDFEBLOCK	pFree;

			ASSERT(pDfb->dfb_NumFree == afpDfeNumFileBlocks[i]);
			pFree = pDfb;
			pDfb = pDfb->dfb_Next;
			AfpFreePAPagedMemory(pFree, BLOCK_ALLOC_SIZE);
#if	DBG
			afpDfbAllocCount --;
#endif
		}
	}

	ASSERT (afpDfbAllocCount == 0);
}


/***	AfpFindDfEntryById
 *
 *	Search for an entity based on its AFP Id. returns a pointer to the entry
 *	if found, else null.
 *
 *	Callable from within the Fsp only. The caller should take Swmr lock for
 *	READ.
 *
 *	LOCKS_ASSUMED: vds_idDbAccessLock (SWMR, Shared)
 */
PDFENTRY
AfpFindDfEntryById(
	IN	PVOLDESC	pVolDesc,
	IN	DWORD		Id,
	IN	DWORD		EntityMask
)
{
	PDFENTRY	pDfEntry;
	BOOLEAN		Found = False;

	PAGED_CODE( );

#ifdef	PROFILING
	INTERLOCKED_INCREMENT_LONG(&AfpServerProfile->perf_NumDfeLookupById);
#endif

	if (Id == AFP_ID_ROOT)
	{
		Found = True;
		pDfEntry = pVolDesc->vds_pDfeRoot;
		ASSERT (VALID_DFE(pDfEntry));

#ifdef	PROFILING
		INTERLOCKED_INCREMENT_LONG(&AfpServerProfile->perf_DfeCacheHits);
#endif
	}
	else
	{
		pDfEntry = pVolDesc->vds_pDfeCache[HASH_CACHE_ID(Id)];
		if ((pDfEntry != NULL) && (pDfEntry->dfe_AfpId == Id))
		{
			Found = True;
			ASSERT (VALID_DFE(pDfEntry));
#ifdef	PROFILING
			INTERLOCKED_INCREMENT_LONG(&AfpServerProfile->perf_DfeCacheHits);
#endif
		}
		else
		{
			BOOLEAN	retry = False;

#ifdef	PROFILING
			INTERLOCKED_INCREMENT_LONG(&AfpServerProfile->perf_DfeCacheMisses);
#endif
			if ((EntityMask == DFE_ANY) || (EntityMask == DFE_DIR))
			{
				if (EntityMask == DFE_ANY)
					retry = True;
				pDfEntry = pVolDesc->vds_pDfeDirBuckets[HASH_DIR_ID(Id)];
			}
			else
			{
				pDfEntry = pVolDesc->vds_pDfeFileBuckets[HASH_FILE_ID(Id)];
			}

			do
			{
				for (NOTHING;
					 (pDfEntry != NULL) && (pDfEntry->dfe_AfpId >= Id);
					 pDfEntry = pDfEntry->dfe_NextOverflow)
				{
#ifdef	PROFILING
					INTERLOCKED_INCREMENT_LONG(&AfpServerProfile->perf_DfeDepthTraversed);
#endif
					ASSERT(VALID_DFE(pDfEntry));

					if (pDfEntry->dfe_AfpId < Id)
					{
						break;		// Did not find
					}

					if (pDfEntry->dfe_AfpId == Id)
					{
						pVolDesc->vds_pDfeCache[HASH_CACHE_ID(Id)] = pDfEntry;
						Found = True;
						break;
					}
				}

				if (Found)
				{
					break;
				}

				if (retry)
				{
					ASSERT(EntityMask == DFE_ANY);
					pDfEntry = pVolDesc->vds_pDfeFileBuckets[HASH_FILE_ID(Id)];
				}
				retry ^= True;

			} while (!retry);
		}
	}

	if (Found)
	{
		afpValidateDFEType(pDfEntry, EntityMask);
		if (pDfEntry != NULL)
		{
			afpUpdateDfeAccessTime(pVolDesc, pDfEntry);
		}
	}
	else
	{
		DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
				("AfpFindDfEntryById: Not found for id %lx, entity %d\n",
				Id, EntityMask));
		pDfEntry = NULL;
	}

	return pDfEntry;
}


/***	AfpFindEntryByUnicodeName
 *
 *	Search for an entity based on a Unicode name and its parent dfentry.
 *	Returns a pointer to the entry if found, else null.  If lookup is by
 *	longname, we just need to search the parent's children's names as
 *	stored in the database.  If lookup is by shortname, we first assume
 *	that longname == shortname.  If we don't find it in the database, we
 *	must query the filesystem for the longname, then search again.
 *
 *	Callable from within the Fsp only. The caller should take Swmr lock for
 *	READ.
 *
 *	LOCKS_ASSUMED: vds_idDbAccessLock (SWMR, Shared)
 */
PDFENTRY
AfpFindEntryByUnicodeName(
	IN	PVOLDESC		pVolDesc,
	IN	PUNICODE_STRING	pName,
	IN	DWORD			PathType,	// short or long name
	IN	PDFENTRY		pDfeParent,	// pointer to parent DFENTRY
	IN	DWORD			EntityMask	// find a file,dir or either
)
{
	PDFENTRY		pDfEntry;

	PAGED_CODE( );

#ifdef	PROFILING
	INTERLOCKED_INCREMENT_LONG(&AfpServerProfile->perf_NumDfeLookupByName);
#endif
	do
	{
		afpFindDFEByUnicodeNameInSiblingList(pVolDesc,
											 pDfeParent,
											 pName,
											 &pDfEntry,
											 EntityMask);

		if ((pDfEntry == NULL) && (PathType == AFP_SHORTNAME))
		{
			AFPSTATUS		Status;
			FILESYSHANDLE	hDir;
			UNICODE_STRING	HostPath;
			UNICODE_STRING	ULongName;
			WCHAR			LongNameBuf[AFP_LONGNAME_LEN+1];

			// AFP does not allow use of the volume root shortname (IA p.13-13)
			if (DFE_IS_PARENT_OF_ROOT(pDfeParent))
			{
				pDfEntry = NULL;
				break;
			}

			AfpSetEmptyUnicodeString(&HostPath, 0, NULL);

			if (!DFE_IS_ROOT(pDfeParent))
			{
				// Get the volume relative path of the parent dir
				if (!NT_SUCCESS(AfpHostPathFromDFEntry(pDfeParent,
													   0,
													   &HostPath)))
				{
					pDfEntry = NULL;
					break;
				}
			}

			// Open the parent directory
			hDir.fsh_FileHandle = NULL;
			Status = AfpIoOpen(&pVolDesc->vds_hRootDir,
								AFP_STREAM_DATA,
								FILEIO_OPEN_DIR,
								DFE_IS_ROOT(pDfeParent) ?
										&UNullString : &HostPath,
								FILEIO_ACCESS_READ,
								FILEIO_DENY_NONE,
								False,
								&hDir);

			if (HostPath.Buffer != NULL)
				AfpFreeMemory(HostPath.Buffer);

			if (!NT_SUCCESS(Status))
			{
				pDfEntry = NULL;
				break;
			}

			// get the LongName associated with this file/dir
			AfpSetEmptyUnicodeString(&ULongName, sizeof(LongNameBuf), LongNameBuf);
			Status = AfpIoQueryLongName(&hDir, pName, &ULongName);
			AfpIoClose(&hDir);
			if (!NT_SUCCESS(Status) ||
				EQUAL_UNICODE_STRING(&ULongName, pName, True))
			{
				pDfEntry = NULL;
				break;
			}

			afpFindDFEByUnicodeNameInSiblingList(pVolDesc,
												 pDfeParent,
												 &ULongName,
												 &pDfEntry,
												 EntityMask);
		} // end else if SHORTNAME
	} while (False);

	return pDfEntry;
}


/***	afpGetNextId
 *
 *	Get the next assignable id for a file/directory. This is a seperate
 *	routine so that AfpAddDfEntry can be paged. Only update the dirty bit
 *	and LastModified time if no new id is assigned.
 *
 *	LOCKS:	vds_VolLock (SPIN)
 */
LOCAL DWORD FASTCALL
afpGetNextId(
	IN	PVOLDESC	pVolDesc
)
{
	KIRQL	OldIrql;
	DWORD	afpId;

	ACQUIRE_SPIN_LOCK(&pVolDesc->vds_VolLock, &OldIrql);

	if (pVolDesc->vds_LastId == AFP_MAX_DIRID)
	{
		// errorlog the case where the assigned Id has wrapped around.
		// call product suppport and have them tell you to copy
		// all the files from one volume onto another volume FROM A MAC
		RELEASE_SPIN_LOCK(&pVolDesc->vds_VolLock, OldIrql);
		AFPLOG_ERROR(AFPSRVMSG_MAX_DIRID,
					 STATUS_UNSUCCESSFUL,
					 NULL,
					 0,
					 &pVolDesc->vds_Name);
		return 0;
	}

	afpId = ++ pVolDesc->vds_LastId;
	pVolDesc->vds_Flags |= VOLUME_IDDBHDR_DIRTY;

	if (IS_VOLUME_NTFS(pVolDesc))
	{
		AfpGetCurrentTimeInMacFormat(&pVolDesc->vds_ModifiedTime);
	}

	RELEASE_SPIN_LOCK(&pVolDesc->vds_VolLock, OldIrql);

	return afpId;
}


/***	afpFindEntryByNtName
 *
 *	Search for an entity based on a Nt name (which could include names > 31
 *  chars or shortnames) and its parent dfentry.
 *	Returns a pointer to the entry if found, else null.
 *
 *	If we don't find it in the database, we query the filesystem for the
 *  longname (in the AFP sense), then search again based on this name.
 *
 *	Callable from within the Fsp only. The caller should take Swmr lock for
 *	READ.
 *
 *	It has been determined that:
 *	a, The name is longer than 31 chars	OR
 *	b, The name lookup in the IdDb has failed.
 *
 *	LOCKS_ASSUMED: vds_idDbAccessLock (SWMR, Exclusive)
 */
PDFENTRY
afpFindEntryByNtName(
	IN	PVOLDESC			pVolDesc,
	IN	PUNICODE_STRING		pName,
	IN	PDFENTRY			pParentDfe	// pointer to parent DFENTRY
)
{
	AFPSTATUS		Status;
	WCHAR			wbuf[AFP_LONGNAME_LEN+1];
	WCHAR			HostPathBuf[BIG_PATH_LEN];
	UNICODE_STRING	uLongName;
	UNICODE_STRING	HostPath;
	FILESYSHANDLE	hDir;
	PDFENTRY		pDfEntry = NULL;

	PAGED_CODE( );

	ASSERT(pParentDfe != NULL);
	ASSERT(pName->Length > 0);
	do
	{
		AfpSetEmptyUnicodeString(&HostPath, sizeof(HostPathBuf), HostPathBuf);

		if (!DFE_IS_ROOT(pParentDfe))
		{
			// Get the volume relative path of the parent dir
			if (!NT_SUCCESS(AfpHostPathFromDFEntry(pParentDfe,
												   0,
												   &HostPath)))
			{
				pDfEntry = NULL;
				break;
			}

		}

		// Open the parent directory
		// NOTE: We CANNOT use the vds_hRootDir handle to enumerate for this
		// purpose.  We MUST open another handle to the root dir because
		// the FileName parameter will be ignored on all subsequent enumerates
		// on a handle.  Therefore we must open a new handle for each
		// enumerate that we want to do for any directory.  When the handle
		// is closed, the 'findfirst' will be cancelled, otherwise we would
		// always be enumerating on the wrong filename!
		hDir.fsh_FileHandle = NULL;
		Status = AfpIoOpen(&pVolDesc->vds_hRootDir,
							AFP_STREAM_DATA,
							FILEIO_OPEN_DIR,
							DFE_IS_ROOT(pParentDfe) ?
								&UNullString : &HostPath,
							FILEIO_ACCESS_NONE,
							FILEIO_DENY_NONE,
							False,
							&hDir);

		if (!NT_SUCCESS(Status))
		{
			pDfEntry = NULL;
			break;
		}

		// get the 'AFP longname' associated with this file/dir.  If the
		// pName is longer than 31 chars, we will know it by its shortname,
		// so query for it's shortname (i.e. the 'AFP longname' we know it
		// by).  If the name is shorter than 31 chars, since we know we
		// didn't find it in our database, then the pName must be the ntfs
		// shortname.  Again, we need to Find the 'AFP longname' that we
		// know it by.
		AfpSetEmptyUnicodeString(&uLongName, sizeof(wbuf), wbuf);
		Status = AfpIoQueryLongName(&hDir, pName, &uLongName);
		AfpIoClose(&hDir);


		if (!NT_SUCCESS(Status) ||
			EQUAL_UNICODE_STRING(&uLongName, pName, True))
		{
			pDfEntry = NULL;

			if ((Status == STATUS_NO_MORE_FILES) ||
				(Status == STATUS_NO_SUCH_FILE))
			{
				// This file must have been deleted.  Since we cannot
				// identify it in our database by the NT name that was
				// passed in, we must reenumerate the parent directory.
				// Anything we don't see on disk that we still have in
				// our database must have been deleted from disk, so get
				// rid of it in the database as well.

				// We must open a DIFFERENT handle to the parent dir since
				// we had already done an enumerate using that handle and
				// searching for a different name.
				hDir.fsh_FileHandle = NULL;
				Status = AfpIoOpen(&pVolDesc->vds_hRootDir,
									AFP_STREAM_DATA,
									FILEIO_OPEN_DIR,
									DFE_IS_ROOT(pParentDfe) ?
										&UNullString : &HostPath,
									FILEIO_ACCESS_NONE,
									FILEIO_DENY_NONE,
									False,
									&hDir);

				if (NT_SUCCESS(Status))
				{
					AfpCacheDirectoryTree(pVolDesc,
										  pParentDfe,
										  REENUMERATE,
										  &hDir,
										  NULL);
					AfpIoClose(&hDir);
				}
			}
			break;
		}

		afpFindDFEByUnicodeNameInSiblingList(pVolDesc,
											 pParentDfe,
											 &uLongName,
											 &pDfEntry,
											 DFE_ANY);
	} while (False);

	if ((HostPath.Buffer != NULL) && (HostPath.Buffer != HostPathBuf))
		AfpFreeMemory(HostPath.Buffer);

	return pDfEntry;
}


/***	afpFindEntryByNtPath
 *
 *	Given a NT path relative to the volume root (which may contain names
 *  > 31 chars or shortnames), look up the entry in the idindex DB.
 *  If the Change Action is FILE_ACTION_ADDED, we want to lookup the entry
 *  for the item's parent dir.  Point the pParent and pTail strings into
 *  the appropriate places in pPath.
 *
 *  Called by the ProcessChangeNotify code when caching information in the DFE.
 *
 *	LOCKS:	vds_VolLock (SPIN)
 */
PDFENTRY
afpFindEntryByNtPath(
	IN	PVOLDESC			pVolDesc,
	IN  DWORD				ChangeAction,	// if ADDED then lookup parent DFE
	IN	PUNICODE_STRING		pPath,
	OUT	PUNICODE_STRING 	pParent,
	OUT	PUNICODE_STRING 	pTail
)
{
	PDFENTRY		pParentDfe, pDfEntry;
	PWSTR			CurPtr, EndPtr;
	USHORT 			Len;
	BOOLEAN			NewComp;

	DBGPRINT(DBG_COMP_CHGNOTIFY, DBG_LEVEL_INFO,
			("afpFindEntryByNtPath: Entered for %Z\n", pPath));

	pParentDfe = pVolDesc->vds_pDfeRoot;
	ASSERT(pParentDfe != NULL);
	ASSERT(pPath->Length >= sizeof(WCHAR));
	ASSERT(pPath->Buffer[0] != L'\\');

	// Start off with Parent and Tail as both empty and modify as we go.
	AfpSetEmptyUnicodeString(pTail, 0, NULL);
#if DBG
	AfpSetEmptyUnicodeString(pParent, 0, NULL);	// Need it for the DBGPRINT down below
#endif

	CurPtr = pPath->Buffer;
	EndPtr = (PWSTR)((PBYTE)CurPtr + pPath->Length);
	NewComp = True;
	for (Len = 0; CurPtr < EndPtr; CurPtr++)
	{
		if (NewComp)
		{
			DBGPRINT(DBG_COMP_CHGNOTIFY, DBG_LEVEL_INFO,
					("afpFindEntryByNtPath: Parent DFE %lx, Old Parent %Z\n",
					pParentDfe, pParent));

			// The previous char seen was a path separator
			NewComp = False;
			*pParent = *pTail;
			pParent->Length =
			pParent->MaximumLength = Len;
			pTail->Length =
			pTail->MaximumLength = (PBYTE)EndPtr - (PBYTE)CurPtr;
			pTail->Buffer = CurPtr;
			Len = 0;

			DBGPRINT(DBG_COMP_CHGNOTIFY, DBG_LEVEL_INFO,
					("afpFindEntryByNtPath: Current Parent %Z, tail %Z\n",
					pParent, pTail));

			if (pParent->Length > 0)
			{
				// Map this name to a DFE. Do the most common case here
				// If the name is <= AFP_LONGNAME_NAME, then check the
				// current parent's children, else go the long route.
				pDfEntry = NULL;
				if (pParent->Length/sizeof(WCHAR) <= AFP_LONGNAME_LEN)
				{
					DBGPRINT(DBG_COMP_CHGNOTIFY, DBG_LEVEL_INFO,
							("afpFindEntryByNtPath: Looking for %Z in parent DFE %lx\n",
							pParent, pParentDfe));
					afpFindDFEByUnicodeNameInSiblingList(pVolDesc,
														 pParentDfe,
														 pParent,
														 &pDfEntry,
														 DFE_DIR);
				}
				if (pDfEntry == NULL)
				{
					pDfEntry = afpFindEntryByNtName(pVolDesc,
													pParent,
													pParentDfe);
				}
				if ((pParentDfe = pDfEntry) == NULL)
				{
					break;
				}
			}
		}

		if (*CurPtr == L'\\')
		{
			// We have encountered a path terminator
			NewComp = True;
		}
		else Len += sizeof(WCHAR);
	}

	// At this point we have pParentDfe & pParent pointing to the parent directory
	// and pTail pointing to the last component. If it is an add operation, we are
	// set, else map the last component to its Dfe
	if ((ChangeAction != FILE_ACTION_ADDED) && (pParentDfe != NULL))
	{
		pDfEntry = NULL;
		if (pTail->Length/sizeof(WCHAR) <= AFP_LONGNAME_LEN)
		{
			afpFindDFEByUnicodeNameInSiblingList(pVolDesc,
												 pParentDfe,
												 pTail,
												 &pDfEntry,
												 DFE_ANY);
		}

		if (pDfEntry == NULL)
		{
			BOOLEAN KeepLooking = True;

			//
			// We couldn't find this item in the database by the name
			// given, which means that we either know it by a different
			// name or it has been deleted, renamed or moved since.
			// If this is a modify change notify, then search for a
			// corresponding DELETED or RENAMED_OLD_NAME change that might
			// be in the changelist by this same name (so can do a fast
			// case sensitive search).
			//
			// This will speed up the case (avoid disk enumerates) where
			// there are a bunch of changes that we are trying to process
			// for an item, but it has since been deleted.  It will prevent
			// us from re-enumerating the disk looking for the longname
			// and then also trying to prune out dead wood with a call to
			// AfpCacheDirectoryTree(REENUMERATE).
			//
			// This will pimp the case where a PC has made a change using
			// a different name than we know it by (and not deleted or
			// renamed the thing), but this case takes a back seat to the
			// other case that could happen when a mac app does a File-Save
			// doing a lot of writes followed by renames (or ExchangeFiles)
			// and deletes.
			//

			if ( (ChangeAction == FILE_ACTION_MODIFIED)  ||
				 (ChangeAction == FILE_ACTION_MODIFIED_STREAM) )
			{
				KIRQL			OldIrql;
				PLIST_ENTRY		pLink = &pVolDesc->vds_ChangeNotifyLookAhead;
				PVOL_NOTIFY 	pVolNotify;
				UNICODE_STRING	UName;
				PFILE_NOTIFY_INFORMATION	pFNInfo;

				ACQUIRE_SPIN_LOCK(&pVolDesc->vds_VolLock, &OldIrql);

				while (pLink->Flink != &pVolDesc->vds_ChangeNotifyLookAhead)
				{
					pLink = pLink->Flink;
					pVolNotify = CONTAINING_RECORD(pLink, VOL_NOTIFY, vn_DelRenLink);
					pFNInfo = (PFILE_NOTIFY_INFORMATION) (pVolNotify + 1);

					AfpInitUnicodeStringWithNonNullTerm(&UName,
														(USHORT)pFNInfo->FileNameLength,
														pFNInfo->FileName);

					if (EQUAL_UNICODE_STRING_CS(pPath, &UName))
					{
						KeepLooking = False;
						DBGPRINT(DBG_COMP_CHGNOTIFY, DBG_LEVEL_WARN,
								("afpFindEntryByNtPath: Found later REMOVE for %Z, Ignoring change\n", pPath));
						break;
					}
				}

				RELEASE_SPIN_LOCK(&pVolDesc->vds_VolLock, OldIrql);
			}

			if (KeepLooking)
			{
				pDfEntry = afpFindEntryByNtName(pVolDesc,
												pTail,
												pParentDfe);
			}
		}
		pParentDfe = pDfEntry;
	}

	// pParent is pointing to the parent component, we need the entire volume
	// relative path. Make it so. Do not bother if pParentDfe is NULL. Make
	// sure that we handle the case where there is only one component
	if (pParentDfe != NULL)
	{
		*pParent = *pPath;
		pParent->Length = pPath->Length - pTail->Length;
		if (pPath->Length > pTail->Length)
			pParent->Length -= sizeof(L'\\');
	}

	return pParentDfe;
}


/***	AfpAddDfEntry
 *
 *	Triggerred by the creation of a file/directory or discovery of a file/dir
 *	from an enumerate or pathmapping operation. If no AFP Id is supplied, a new
 *	id is assigned to this entity.  If an AFP Id is supplied (we know the Id
 *	is within our current range and does not collide with any other entry), then
 *	we use that Id.  An entry is created and linked in to the database and hash
 *	table. If this is an NTFS volume, the Id database header is marked
 *	dirty if we assigned a new AFP Id, and the volume modification time is
 *	updated.  The hash table overflow entries are sorted in descending AFP Id
 *	order.
 *
 *	Callable from within the Fsp only. The caller should take Swmr lock for
 *	WRITE.
 *
 *	LOCKS_ASSUMED: vds_idDbAccessLock (SWMR, Exclusive)
 */
PDFENTRY
AfpAddDfEntry(
	IN	PVOLDESC			pVolDesc,
	IN	PDFENTRY			pDfeParent,
	IN	PUNICODE_STRING 	pUName,
	IN	BOOLEAN				fDirectory,
	IN	DWORD				AfpId		OPTIONAL
)
{
	PDFENTRY	pDfEntry;
	BOOLEAN		fSuccess;

	PAGED_CODE();

	ASSERT(DFE_IS_DIRECTORY(pDfeParent));

	do
	{
		if ((pDfEntry = ALLOC_DFE(USIZE_TO_INDEX(pUName->Length), fDirectory)) == NULL)
		{
			break;
		}

		pDfEntry->dfe_Flags = 0;

		if (!ARGUMENT_PRESENT(AfpId))
			AfpId = afpGetNextId(pVolDesc);

		if (AfpId == 0)
		{
			// errorlog the case where the assigned Id has wrapped around.
			// call product suppport and have them tell you to copy
			// all the files from one volume onto another volume FROM A MAC
			//
			// NOTE:	How about a utility which will re-assign new ids on
			//			a volume after stopping the server ? A whole lot more
			//			palatable idea.
			FREE_DFE(pDfEntry);
			pDfEntry = NULL;
			break;
		}

		pDfEntry->dfe_AfpId = AfpId;

		// Initialize its parent
		pDfEntry->dfe_Parent = pDfeParent;

		// Copy the name
		AfpCopyUnicodeString(&pDfEntry->dfe_UnicodeName,
							 pUName);

		// And hash it
		afpHashUnicodeName(&pDfEntry->dfe_UnicodeName, &pDfEntry->dfe_NameHash);

		pDfEntry->dfe_NextOverflow = NULL;
		pDfEntry->dfe_NextSibling = NULL;

		// Now link this into the hash bucket, sorted in AFP Id descending order
		// and update the cache
		DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
				("AfpAddDfEntry: Linking DFE %lx( Id %ld) for %Z into %s bucket %ld\n",
				pDfEntry, pDfEntry->dfe_AfpId, pUName,
				fDirectory ? "Dir" : "File",
				fDirectory ? HASH_DIR_ID(AfpId) : HASH_FILE_ID(AfpId)));

		if (fDirectory)
		{
			DFE_SET_DIRECTORY(pDfEntry, pDfeParent->dfe_DirDepth);
		}
		else
		{
			DFE_SET_FILE(pDfEntry);
		}

		afpInsertDFEInHashBucket(pVolDesc, pDfEntry, fDirectory, &fSuccess);
		if (!fSuccess)
		{
			/* Out of id space - bail out */
			FREE_DFE(pDfEntry);
			pDfEntry = NULL;
			break;
		}

		if (fDirectory)
		{
			if ((pDfeParent->dfe_DirOffspring == 0) && !EXCLUSIVE_VOLUME(pVolDesc))
			{
				DWORD requiredLen;

				// check to see if we need to reallocate a bigger notify buffer.
				// The buffer must be large enough to hold a rename
				// notification (which will contain 2 FILE_NOTIFY_INFORMATION
				// structures) for the deepest element in the directory tree.
				requiredLen = (((pDfEntry->dfe_DirDepth + 1) *
							  ((AFP_FILENAME_LEN + 1) * sizeof(WCHAR))) +
							  FIELD_OFFSET(FILE_NOTIFY_INFORMATION, FileName)) * 2 ;
				if ( requiredLen > pVolDesc->vds_CurNotifyBufLen)
				{
					pVolDesc->vds_RequiredNotifyBufLen = requiredLen;
					DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
							("AfpAddDfEntry: Required Notify Buf size increased to %ld (depth %ld)\n",
							requiredLen, pDfEntry->dfe_DirDepth));
				}
			}
			pDfeParent->dfe_DirOffspring ++;
			pDfEntry->dfe_DirOffspring = 0;
			pDfEntry->dfe_FileOffspring = 0;
			pVolDesc->vds_NumDirDfEntries ++;

#ifdef AGE_DFES
			// These fields are relevant to directories only
			pDfEntry->dfe_pDirEntry->de_LastAccessTime = BEGINNING_OF_TIME;
			pDfEntry->dfe_pDirEntry->de_ChildForkOpenCount = 0;
#endif
			ASSERT((FIELD_OFFSET(DIRENTRY, de_ChildFile) -
					FIELD_OFFSET(DIRENTRY, de_ChildDir)) == sizeof(PVOID));

			// Insert it into its sibling chain
			afpInsertDirDFEInSiblingList(pDfeParent, pDfEntry);
		}
		else
		{
			pDfeParent->dfe_FileOffspring ++;
			pDfEntry->dfe_DataLen = 0;
			pDfEntry->dfe_RescLen = 0;
			pVolDesc->vds_NumFileDfEntries ++;

			// Insert it into its sibling chain
			afpInsertFileDFEInSiblingList(pDfeParent, pDfEntry);
		}

	} while (False);

	return pDfEntry;
}


/***	AfpRenameDfEntry
 *
 *	Triggered by a rename of a file/directory.  If the new name is longer than
 *	the current name, the DFEntry is freed and then reallocated to fit the new
 *	name.  A renamed file/dir must retain its original ID.
 *
 *	Callable from within the Fsp only. The caller should take Swmr lock for
 *	WRITE.
 *
 *	LOCKS:	vds_VolLock (SPIN) for updating the IdDb header.
 *	LOCKS_ASSUMED: vds_idDbAccessLock (SWMR, Exclusive)
 *	LOCK_ORDER: VolDesc lock after IdDb Swmr.
 *
 */
PDFENTRY
AfpRenameDfEntry(
	IN	PVOLDESC			pVolDesc,
	IN	PDFENTRY			pDfEntry,
	IN	PUNICODE_STRING		pNewName
)
{
	BOOLEAN		fDirectory;
	PDFENTRY	pNewDfEntry = pDfEntry;
	DWORD		OldIndex, NewIndex;

	PAGED_CODE( );

	ASSERT((pDfEntry != NULL) && (pNewName != NULL) && (pVolDesc != NULL));

	do
	{
		fDirectory = DFE_IS_DIRECTORY(pDfEntry);
		OldIndex = USIZE_TO_INDEX(pDfEntry->dfe_UnicodeName.MaximumLength);
		NewIndex = USIZE_TO_INDEX(pNewName->Length);
		if (OldIndex != NewIndex)
		{
			if ((pNewDfEntry = ALLOC_DFE(NewIndex, fDirectory)) == NULL)
			{
				pNewDfEntry = NULL;
				break;
			}

			// Careful here how the structures are copied
			RtlCopyMemory(pNewDfEntry,
						  pDfEntry,
						  FIELD_OFFSET(DFENTRY, dfe_CopyUpto));

			// Update the cache
			pVolDesc->vds_pDfeCache[HASH_CACHE_ID(pDfEntry->dfe_AfpId)] = pNewDfEntry;

			// fix up the overflow links from the hash table
			AfpUnlinkDouble(pDfEntry,
							dfe_NextOverflow,
							dfe_PrevOverflow);
			if (pDfEntry->dfe_NextOverflow != NULL)
			{
				AfpInsertDoubleBefore(pNewDfEntry,
									  pDfEntry->dfe_NextOverflow,
									  dfe_NextOverflow,
									  dfe_PrevOverflow);
			}
			else
			{
				*(pDfEntry->dfe_PrevOverflow) = pNewDfEntry;
				pNewDfEntry->dfe_NextOverflow = NULL;
			}

			// now fix any of this thing's children's parent pointers.
			if (fDirectory)
			{
				PDFENTRY	pTmp;
				LONG		i;
	
				// First copy the DirEntry structure
				if (fDirectory)
				{
					*pNewDfEntry->dfe_pDirEntry = *pDfEntry->dfe_pDirEntry;
				}

				// Start with Dir children
				if ((pTmp = pDfEntry->dfe_pDirEntry->de_ChildDir) != NULL)
				{
					// First fix up the first child's PrevSibling pointer
					pTmp->dfe_PrevSibling = &pNewDfEntry->dfe_pDirEntry->de_ChildDir;
	
					for (NOTHING;
						 pTmp != NULL;
						 pTmp = pTmp->dfe_NextSibling)
					{
						ASSERT(pTmp->dfe_Parent == pDfEntry);
						pTmp->dfe_Parent = pNewDfEntry;
					}
				}
	
				// Repeat for File childs as well
				for (i = 0; i < MAX_CHILD_HASH_BUCKETS; i++)
				{
					if ((pTmp = pDfEntry->dfe_pDirEntry->de_ChildFile[i]) != NULL)
					{
						// First  fix up the first child's PrevSibling pointer
						pTmp->dfe_PrevSibling = &pNewDfEntry->dfe_pDirEntry->de_ChildFile[i];
	
						for (NOTHING;
							 pTmp != NULL;
							 pTmp = pTmp->dfe_NextSibling)
						{
							ASSERT(pTmp->dfe_Parent == pDfEntry);
							pTmp->dfe_Parent = pNewDfEntry;
						}
					}
				}
			}
		}

		// Now fix the sibling relationships. Note that this needs to be done
		// regardless of whether a new dfe was created since these depend on
		// name hash which has potentially changed
		AfpUnlinkDouble(pDfEntry,
						dfe_NextSibling,
						dfe_PrevSibling);

		// Copy the new unicode name and create a new hash
		AfpCopyUnicodeString(&pNewDfEntry->dfe_UnicodeName,
							 pNewName);
		afpHashUnicodeName(&pNewDfEntry->dfe_UnicodeName, &pNewDfEntry->dfe_NameHash);

		// Insert it into its sibling chain
		afpInsertDFEInSiblingList(pNewDfEntry->dfe_Parent, pNewDfEntry, fDirectory);

		if (pDfEntry != pNewDfEntry)
			FREE_DFE(pDfEntry);

		AfpVolumeSetModifiedTime(pVolDesc);
	} while (False);

	return pNewDfEntry;
}


/***	AfpMoveDfEntry
 *
 *	Triggered by a move/rename-move of a file/dir.  A moved entity must retain
 *	its AfpId.
 *
 *	Callable from within the Fsp only. The caller should take Swmr lock for
 *	WRITE.
 *
 *	LOCKS:	vds_VolLock (SPIN) for updating the IdDb header.
 *	LOCKS_ASSUMED: vds_idDbAccessLock (SWMR, Exclusive)
 *	LOCK_ORDER: VolDesc lock after IdDb Swmr.
 *
 */
PDFENTRY
AfpMoveDfEntry(
	IN	PVOLDESC			pVolDesc,
	IN	PDFENTRY			pDfEntry,
	IN	PDFENTRY			pNewParentDFE,
	IN	PUNICODE_STRING		pNewName		OPTIONAL
)
{
	SHORT		depthDelta;					// This must be signed
	BOOLEAN		fDirectory;

	PAGED_CODE( );

	ASSERT((pDfEntry != NULL) && (pNewParentDFE != NULL) && (pVolDesc != NULL));

	// do we need to rename the DFEntry ?
	if (ARGUMENT_PRESENT(pNewName) &&
		!EQUAL_UNICODE_STRING(pNewName, &pDfEntry->dfe_UnicodeName, True))
	{
		if ((pDfEntry = AfpRenameDfEntry(pVolDesc,
										 pDfEntry,
										 pNewName)) == NULL)
		{
			return NULL;
		}
	}

	if (pDfEntry->dfe_Parent != pNewParentDFE)
	{
		// unlink the current entry from its parent/sibling associations (but not
		// the overflow hash bucket list since the AfpId has not changed.  The
		// children of this entity being moved (if its a dir and it has any) will
		// remain intact, and move along with the dir)
		AfpUnlinkDouble(pDfEntry, dfe_NextSibling, dfe_PrevSibling);

		fDirectory = DFE_IS_DIRECTORY(pDfEntry);

		// Decrement the old parent's offspring count & increment the new parent
		if (fDirectory)
		{
			ASSERT(pDfEntry->dfe_Parent->dfe_DirOffspring > 0);
			pDfEntry->dfe_Parent->dfe_DirOffspring --;
			pNewParentDFE->dfe_DirOffspring ++;

			// insert it into the new parent's child list
			afpInsertDirDFEInSiblingList(pNewParentDFE, pDfEntry);
		}
		else
		{
			ASSERT(pDfEntry->dfe_Parent->dfe_FileOffspring > 0);
			pDfEntry->dfe_Parent->dfe_FileOffspring --;
			pNewParentDFE->dfe_FileOffspring ++;
#ifdef	AGE_DFES
			if (IS_VOLUME_AGING_DFES(pVolDesc))
			{
				if (pDfEntry->dfe_Flags & DFE_FLAGS_R_ALREADYOPEN)
				{
					pDfEntry->dfe_Parent->dfe_pDirEntry->de_ChildForkOpenCount --;
					pNewParentDFE->dfe_pDirEntry->de_ChildForkOpenCount ++;
				}
				if (pDfEntry->dfe_Flags & DFE_FLAGS_D_ALREADYOPEN)
				{
					pDfEntry->dfe_Parent->dfe_pDirEntry->de_ChildForkOpenCount --;
					pNewParentDFE->dfe_pDirEntry->de_ChildForkOpenCount ++;
				}
			}
#endif
			// insert it into the new parent's child list
			afpInsertFileDFEInSiblingList(pNewParentDFE, pDfEntry);
		}

		pDfEntry->dfe_Parent = pNewParentDFE;

		// If we moved a directory, we must adjust the directory depths of the
		// directory, and all directories below it
		if (fDirectory &&
			((depthDelta = (pNewParentDFE->dfe_DirDepth + 1 - pDfEntry->dfe_DirDepth)) != 0))
		{
			PDFENTRY	pTmp = pDfEntry;

			while (True)
			{
				if ((pTmp->dfe_pDirEntry->de_ChildDir != NULL) &&
					(pTmp->dfe_DirDepth != (pTmp->dfe_Parent->dfe_DirDepth + 1)))
				{
					ASSERT(DFE_IS_DIRECTORY(pTmp));
					pTmp->dfe_DirDepth += depthDelta;
					pTmp = pTmp->dfe_pDirEntry->de_ChildDir;
				}
				else
				{
					ASSERT(DFE_IS_DIRECTORY(pTmp));
					if ((pTmp->dfe_DirDepth != pTmp->dfe_Parent->dfe_DirDepth + 1))
						pTmp->dfe_DirDepth += depthDelta;

					if (pTmp == pDfEntry)
						break;
					else if (pTmp->dfe_NextSibling != NULL)
						 pTmp = pTmp->dfe_NextSibling;
					else pTmp = pTmp->dfe_Parent;
				}
			}
		}
	}

	AfpVolumeSetModifiedTime(pVolDesc);

	return pDfEntry;
}


/***	AfpDeleteDfEntry
 *
 *	Trigerred by the deletion of a file/directory. The entry as well as the
 *	index is unlinked and freed.  If we are deleting a directory that is not
 *	empty, the entire directory tree underneath is deleted as well.  Note when
 *	implementing FPDelete, always attempt the delete from the actual file system
 *	first, then delete from the IdDB if that succeeds.
 *
 *	Callable from within the Fsp only. The caller should take Swmr lock for
 *	WRITE.
 *
 *	LOCKS:	vds_VolLock (SPIN) for updating the IdDb header.
 *	LOCKS_ASSUMED: vds_idDbAccessLock (SWMR, Exclusive)
 *	LOCK_ORDER: VolDesc lock after IdDb Swmr.
 */
VOID FASTCALL
AfpDeleteDfEntry(
	IN	PVOLDESC	pVolDesc,
	IN	PDFENTRY	pDfEntry
)
{
	PDFENTRY	pDfeParent = pDfEntry->dfe_Parent;
	LONG		i;
	BOOLEAN		Prune = False;

	PAGED_CODE( );

	ASSERT(pDfeParent != NULL);

	if (DFE_IS_DIRECTORY(pDfEntry))
	{
		for (i = 0; i < MAX_CHILD_HASH_BUCKETS; i++)
		{
			if (pDfEntry->dfe_pDirEntry->de_ChildFile[i] != NULL)
			{
				Prune = True;
				break;
			}
		}
		if ((pDfEntry->dfe_pDirEntry->de_ChildDir != NULL) || Prune)
		{
			// This will happen if a PC user deletes a tree behind our back
			AfpPruneIdDb(pVolDesc, pDfEntry);
		}
		ASSERT(pDfeParent->dfe_DirOffspring > 0);
		pDfeParent->dfe_DirOffspring --;
	}
	else
	{
		ASSERT(pDfeParent->dfe_FileOffspring > 0);
		pDfeParent->dfe_FileOffspring --;

		// The Finder is braindead about deleting APPL mappings (it deletes
		// the file before deleting the APPL mapping so always gets
		// ObjectNotFound error for RemoveAPPL, and leaves turd mappings).
		if (pDfEntry->dfe_FinderInfo.fd_TypeD == *(PDWORD)"APPL")
		{
			AfpRemoveAppl(pVolDesc,
 						  pDfEntry->dfe_FinderInfo.fd_CreatorD,
						  pDfEntry->dfe_AfpId);
		}

	}

	// Unlink it now from the hash table
	DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
			("AfpDeleteDfEntry: Unlinking from the hash table\n") );
	AfpUnlinkDouble(pDfEntry,
					dfe_NextOverflow,
					dfe_PrevOverflow);

	// Make sure we get rid of the cache if valid
	if (pVolDesc->vds_pDfeCache[HASH_CACHE_ID(pDfEntry->dfe_AfpId)] == pDfEntry)
		pVolDesc->vds_pDfeCache[HASH_CACHE_ID(pDfEntry->dfe_AfpId)] = NULL;

	// Seperate it now from its siblings
	DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
			("AfpDeleteDfEntry: Unlinking from the sibling list\n") );
	AfpUnlinkDouble(pDfEntry,
					dfe_NextSibling,
					dfe_PrevSibling);

	(DFE_IS_DIRECTORY(pDfEntry)) ?
		pVolDesc->vds_NumDirDfEntries -- :
		pVolDesc->vds_NumFileDfEntries --;

	FREE_DFE(pDfEntry);
	AfpVolumeSetModifiedTime(pVolDesc);
}


/***	AfpPruneIdDb
 *
 *	Lops off a branch of the IdDb.  Called by network trash code when
 *	cleaning out the trash directory, or by directory enumerate code that
 *	has discovered a directory has been 'delnoded' by a PC user.  The
 *	IdDb sibling tree is traversed, and each node under the pDfeTarget node
 *	is deleted from the database and freed.  pDfeTarget itself is NOT
 *	deleted.  If necessary, the caller should delete the target itself.
 *
 *	Callable from within the Fsp only. The caller should take Swmr lock for
 *	WRITE.
 *
 *	LOCKS:	vds_VolLock (SPIN) for updating the IdDb header.
 *	LOCKS_ASSUMED: vds_idDbAccessLock (SWMR, Exclusive)
 *	LOCK_ORDER: VolDesc lock after IdDb Swmr.
 */
VOID FASTCALL
AfpPruneIdDb(
	IN	PVOLDESC	pVolDesc,
	IN	PDFENTRY	pDfeTarget
)
{
	PDFENTRY	pCurDfe = pDfeTarget, pDelDfe;
	LONG		i = 0;

	PAGED_CODE( );

	ASSERT((pVolDesc != NULL) && (pDfeTarget != NULL) &&
			(pDfeTarget->dfe_Flags & DFE_FLAGS_DIR));

	DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
			("AfpPruneIdDb entered...\n") );

	while (True)
	{
		ASSERT(DFE_IS_DIRECTORY(pCurDfe));

		// Delete all the file children of this node first
		for (i = 0; i < MAX_CHILD_HASH_BUCKETS; i++)
		{
			while ((pDelDfe = pCurDfe->dfe_pDirEntry->de_ChildFile[i]) != NULL)
			{
				AfpDeleteDfEntry(pVolDesc, pDelDfe);
			}
		}

		if (pCurDfe->dfe_pDirEntry->de_ChildDir != NULL)
		{
			pCurDfe = pCurDfe->dfe_pDirEntry->de_ChildDir;
		}
		else if (pCurDfe == pDfeTarget)
		{
			return;
		}
		else if (pCurDfe->dfe_NextSibling != NULL)
		{
			pDelDfe = pCurDfe;
			pCurDfe = pCurDfe->dfe_NextSibling;
			AfpDeleteDfEntry(pVolDesc, pDelDfe);
		}
		else
		{
			pDelDfe = pCurDfe;
			pCurDfe = pCurDfe->dfe_Parent;
			AfpDeleteDfEntry(pVolDesc, pDelDfe);
		}
	}
}


/***	AfpExchangeIdEntries
 *
 *	Called by AfpExchangeFiles api.
 *
 *	Callable from within the Fsp only. The caller should take Swmr lock for
 *	WRITE.
 *
 *	LOCKS_ASSUMED: vds_idDbAccessLock (SWMR, Exclusive)
 */
VOID
AfpExchangeIdEntries(
	IN	PVOLDESC	pVolDesc,
	IN	DWORD		AfpId1,
	IN	DWORD		AfpId2
)
{
	PDFENTRY pDFE1, pDFE2;
	DFENTRY	 DFEtemp;

	PAGED_CODE( );

	pDFE1 = AfpFindDfEntryById(pVolDesc, AfpId1, DFE_FILE);
	ASSERT(pDFE1 != NULL);

	pDFE2 = AfpFindDfEntryById(pVolDesc, AfpId2, DFE_FILE);
	ASSERT(pDFE2 != NULL);

	DFEtemp = *pDFE2;

	pDFE2->dfe_Flags = pDFE1->dfe_Flags;
	pDFE2->dfe_BackupTime  = pDFE1->dfe_BackupTime;
	pDFE2->dfe_LastModTime = pDFE1->dfe_LastModTime;
	pDFE2->dfe_DataLen = pDFE1->dfe_DataLen;
	pDFE2->dfe_RescLen = pDFE1->dfe_RescLen;
	pDFE2->dfe_NtAttr  = pDFE1->dfe_NtAttr;
	pDFE2->dfe_AfpAttr = pDFE1->dfe_AfpAttr;

	pDFE1->dfe_Flags = DFEtemp.dfe_Flags;
	pDFE1->dfe_BackupTime  = DFEtemp.dfe_BackupTime;
	pDFE1->dfe_LastModTime = DFEtemp.dfe_LastModTime;
	pDFE1->dfe_DataLen = DFEtemp.dfe_DataLen;
	pDFE1->dfe_RescLen = DFEtemp.dfe_RescLen;
	pDFE1->dfe_NtAttr  = DFEtemp.dfe_NtAttr;
	pDFE1->dfe_AfpAttr = DFEtemp.dfe_AfpAttr;
}


/***	AfpEnumerate
 *
 *	Enumerates files and dirs in a directory using the IdDb.
 *	An array of ENUMDIR structures is returned which represent
 *	the enumerated files and dirs.
 *
 *	Short Names
 *	ProDos Info
 *	Offspring count
 *	Permissions/Owner Id/Group Id
 *
 *	LOCKS: vds_idDbAccessLock (SWMR, Shared)
 *
 */
AFPSTATUS
AfpEnumerate(
	IN	PCONNDESC			pConnDesc,
	IN	DWORD				ParentDirId,
	IN	PANSI_STRING		pPath,
	IN	DWORD				BitmapF,
	IN	DWORD				BitmapD,
	IN	BYTE				PathType,
	IN	DWORD				DFFlags,
	OUT PENUMDIR *			ppEnumDir
)
{
	PENUMDIR		pEnumDir;
	PDFENTRY		pDfe, pTmp;
	PEIT			pEit;
	AFPSTATUS		Status;
	PATHMAPENTITY	PME;
	BOOLEAN			NeedHandle = False;
	FILEDIRPARM		FDParm;
	PVOLDESC		pVolDesc = pConnDesc->cds_pVolDesc;
	LONG			EnumCount;
	BOOLEAN			ReleaseSwmr = False, NeedWriteLock = False;

	PAGED_CODE( );

	DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
			("AfpEnumerate Entered\n"));

	do
	{
		// Check if this enumeration matches the current enumeration
		if ((pEnumDir = pConnDesc->cds_pEnumDir) != NULL)
		{
			if ((pEnumDir->ed_ParentDirId == ParentDirId) &&
				(pEnumDir->ed_PathType == PathType) &&
				(pEnumDir->ed_TimeStamp >= pVolDesc->vds_ModifiedTime) &&
				(pEnumDir->ed_Bitmap == (BitmapF + (BitmapD << 16))) &&
				(((pPath->Length == 0) && (pEnumDir->ed_PathName.Length == 0)) ||
				 RtlCompareMemory(pEnumDir->ed_PathName.Buffer,
								 pPath->Buffer,
								 pPath->Length)))
			{
				DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
						("AfpEnumerate found cache hit\n"));
				INTERLOCKED_INCREMENT_LONG(&AfpServerStatistics.stat_EnumCacheHits);
				*ppEnumDir = pEnumDir;
				Status = AFP_ERR_NONE;
				break;
			}

			// Does not match, cleanup the previous entry
			AfpFreeMemory(pEnumDir);
			pConnDesc->cds_pEnumDir = NULL;
		}

		INTERLOCKED_INCREMENT_LONG(&AfpServerStatistics.stat_EnumCacheMisses);
		DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
				("AfpEnumerate creating new cache\n"));

		// We have no current enumeration. Create one now
		*ppEnumDir = NULL;
		AfpInitializeFDParms(&FDParm);
		AfpInitializePME(&PME, 0, NULL);

		if (IS_VOLUME_NTFS(pVolDesc))
		{
			NeedHandle = True;
		}
		Status = AfpMapAfpPathForLookup(pConnDesc,
										ParentDirId,
										pPath,
										PathType,
										DFE_DIR,
										DIR_BITMAP_DIRID |
											DIR_BITMAP_GROUPID |
											DIR_BITMAP_OWNERID |
											DIR_BITMAP_ACCESSRIGHTS |
											FD_INTERNAL_BITMAP_OPENACCESS_READCTRL |
											DIR_BITMAP_OFFSPRINGS,
										NeedHandle ? &PME : NULL,
										&FDParm);

		if (Status != AFP_ERR_NONE)
		{
			if (Status == AFP_ERR_OBJECT_NOT_FOUND)
				Status = AFP_ERR_DIR_NOT_FOUND;
			break;
		}

		if (NeedHandle)
		{
			AfpIoClose(&PME.pme_Handle);
		}

		// For admin, set all access bits
		if (pConnDesc->cds_pSda->sda_ClientType == SDA_CLIENT_ADMIN)
		{
			FDParm._fdp_UserRights = DIR_ACCESS_ALL | DIR_ACCESS_OWNER;
		}

		if ((BitmapF != 0) && (FDParm._fdp_UserRights & DIR_ACCESS_READ))
			DFFlags |= DFE_FILE;
		if ((BitmapD != 0) && (FDParm._fdp_UserRights & DIR_ACCESS_SEARCH))
			DFFlags |= DFE_DIR;

		// Catch access denied error here
		if (DFFlags == 0)
		{
			Status = AFP_ERR_ACCESS_DENIED;
			break;
		}

		// All is hunky-dory so far, go ahead with the enumeration now

#ifdef GET_CORRECT_OFFSPRING_COUNTS
	take_swmr_for_enum:
#endif
		NeedWriteLock ?
			AfpSwmrAcquireExclusive(&pVolDesc->vds_IdDbAccessLock) :
			AfpSwmrAcquireShared(&pVolDesc->vds_IdDbAccessLock);
		ReleaseSwmr = True;

		// Lookup the dfentry of the AfpIdEnumDir
		if ((pDfe = AfpFindDfEntryById(pVolDesc,
										FDParm._fdp_AfpId,
										DFE_DIR)) == NULL)
		{
			Status = AFP_ERR_OBJECT_NOT_FOUND;
			break;
		}

		// Allocate a ENUMDIR structure and initialize it
		EnumCount = 0;
		if (DFFlags & DFE_DIR)
			EnumCount += (DWORD)(pDfe->dfe_DirOffspring);
		if (DFFlags & DFE_FILE)
			EnumCount += (DWORD)(pDfe->dfe_FileOffspring);

		if (EnumCount == 0)
		{
			Status = AFP_ERR_OBJECT_NOT_FOUND;
			break;
		}

		if ((pEnumDir = (PENUMDIR)AfpAllocNonPagedMemory(sizeof(ENUMDIR) +
														 pPath->MaximumLength +
														 EnumCount*sizeof(EIT))) == NULL)
		{
			Status = AFP_ERR_OBJECT_NOT_FOUND;
			break;
		}

		pEnumDir->ed_ParentDirId = ParentDirId;
		pEnumDir->ed_ChildCount = EnumCount;
		pEnumDir->ed_PathType = PathType;
		pEnumDir->ed_Bitmap = (BitmapF + (BitmapD << 16));
		pEnumDir->ed_BadCount = 0;
		pEnumDir->ed_pEit = pEit = (PEIT)((PBYTE)pEnumDir + sizeof(ENUMDIR));
		AfpSetEmptyAnsiString(&pEnumDir->ed_PathName,
							  pPath->MaximumLength,
							  (PBYTE)pEnumDir +
									sizeof(ENUMDIR) +
									EnumCount*sizeof(EIT));
		RtlCopyMemory(pEnumDir->ed_PathName.Buffer,
					  pPath->Buffer,
					  pPath->Length);

		*ppEnumDir = pConnDesc->cds_pEnumDir = pEnumDir;

		// Now copy the enum parameters (Afp Id and file/dir flag) of
		// each of the children, files first
		if (DFFlags & DFE_FILE)
		{
			LONG	i;

			for (i = 0; i < MAX_CHILD_HASH_BUCKETS; i++)
			{
				for (pTmp = pDfe->dfe_pDirEntry->de_ChildFile[i];
					 pTmp != NULL;
					 pTmp = pTmp->dfe_NextSibling, pEit ++)
				{
					ASSERT(!DFE_IS_DIRECTORY(pTmp));

					pEit->eit_Id = pTmp->dfe_AfpId;
					pEit->eit_Flags = DFE_FILE;
				}
			}
		}
		if (DFFlags & DFE_DIR)
		{
			for (pTmp = pDfe->dfe_pDirEntry->de_ChildDir;
				 pTmp != NULL;
				 pTmp = pTmp->dfe_NextSibling, pEit ++)
			{
				ASSERT(DFE_IS_DIRECTORY(pTmp));

				pEit->eit_Id = pTmp->dfe_AfpId;
				pEit->eit_Flags = DFE_DIR;

#ifdef GET_CORRECT_OFFSPRING_COUNTS
				// We are returning a directory offspring, make sure
				// that it has its children cached in so we get the correct
				// file and dir offspring counts for it, otherwise Finder
				// 'view by name' doesn't work correctly because it sees
				// zero as the offspring count and clicking on the triangle
				// shows nothing since it tries to be smart and doesn't
				// explicitly enumerate that dir if offspring count is zero.
				//
				// This can be a big performance hit if a directory has lots
				// of subdirectories which in turn have tons of files.
				//
				// JH - Could we alternately return incorrect information about
				//		files as long as there are directry children. What else
				//		will break ?
				// if (!DFE_CHILDREN_ARE_PRESENT(pTmp) && (pTmp->dfe_DirOffspring == 0))
				if (!DFE_CHILDREN_ARE_PRESENT(pTmp))
				{
					if (!AfpSwmrLockedExclusive(&pVolDesc->vds_IdDbAccessLock) &&
						!AfpSwmrUpgradeToExclusive(&pVolDesc->vds_IdDbAccessLock))
					{
						NeedWriteLock = True;
						AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock);
						ReleaseSwmr = False;
						// We must free the memory here in case the next
						// time we enumerate the dir it has more children
						// than it had the first time -- since we must let go
						// of the swmr here things could change.
						AfpFreeMemory(pEnumDir);
						*ppEnumDir = pConnDesc->cds_pEnumDir = NULL;
						goto take_swmr_for_enum;
					}

					AfpCacheDirectoryTree(pVolDesc,
										  pTmp,
										  GETFILES,
										  NULL,
										  NULL);
				} // if children not cached
#endif
			}
		}

		AfpGetCurrentTimeInMacFormat(&pEnumDir->ed_TimeStamp);
		Status = AFP_ERR_NONE;
	} while (False);

	if (ReleaseSwmr)
		AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock);

	return Status;
}


/***	AfpCatSearch
 *
 *	This routine does a left-hand search on the DFE tree to search for
 *  file/dirs that match the search criteria indicated in pFDParm1 and
 *  pFDParm2.
 *
 *	LOCKS: vds_idDbAccessLock (SWMR, Shared or Exclusive)
 */
AFPSTATUS
AfpCatSearch(
	IN	PCONNDESC			pConnDesc,
	IN	PCATALOGPOSITION	pCatPosition,
	IN	DWORD				Bitmap,
	IN	DWORD				FileBitmap,
	IN	DWORD				DirBitmap,
	IN	PFILEDIRPARM		pFDParm1,
	IN	PFILEDIRPARM		pFDParm2,
	IN	PUNICODE_STRING		pMatchString	OPTIONAL,
	IN OUT	PDWORD			pCount,
	IN  SHORT				Buflen,
	OUT	PSHORT				pSizeLeft,
	OUT	PBYTE				pResults,
	OUT	PCATALOGPOSITION	pNewCatPosition
)
{
	PVOLDESC	pVolDesc = pConnDesc->cds_pVolDesc;
	PDFENTRY	pCurParent, pCurFile;
	BOOLEAN		MatchFiles = True, MatchDirs = True, NewSearch = False;
	BOOLEAN		HaveSeeFiles, HaveSeeFolders, CheckAccess = False;
	AFPSTATUS	Status = AFP_ERR_NONE;
	LONG		i;
	DWORD		ActCount = 0;
	SHORT		SizeLeft = Buflen;
	PSWMR		pSwmr = &(pConnDesc->cds_pVolDesc->vds_IdDbAccessLock);
	USHORT		Flags;
	UNICODE_STRING	CurPath;

	typedef struct _SearchEntityPkt
	{
		BYTE	__Length;
		BYTE	__FileDirFlag;
		// The real parameters follow
	} SEP, *PSEP;
	PSEP 	pSep;

	PAGED_CODE( );

	pSep = (PSEP)pResults;
	RtlZeroMemory(pNewCatPosition, sizeof(CATALOGPOSITION));

  CatSearchStart:
	Flags = pCatPosition->cp_Flags;
	pCurFile = NULL;
	i = MAX_CHILD_HASH_BUCKETS;

	if (Flags & CATFLAGS_WRITELOCK_REQUIRED)
	{
		ASSERT(Flags == (CATFLAGS_SEARCHING_FILES | CATFLAGS_WRITELOCK_REQUIRED));
		AfpSwmrAcquireExclusive(pSwmr);
		Flags &= ~CATFLAGS_WRITELOCK_REQUIRED;
	}
	else
		AfpSwmrAcquireShared(pSwmr);

	if (Flags == 0)
	{
		//
		// Start search from beginning of catalog (i.e. the root directory)
		//
		i = 0;
		pCurParent = pVolDesc->vds_pDfeRoot;
		pCurFile = pCurParent->dfe_pDirEntry->de_ChildFile[0];
		if (IS_VOLUME_NTFS(pVolDesc))
			CheckAccess = True;
		Flags = CATFLAGS_SEARCHING_FILES;
		NewSearch = True;
	}
	else
	{
		//
		// This is a continuation of a previous search, pickup where we
		// left off
		//

		AFPTIME CurrentTime;

		AfpGetCurrentTimeInMacFormat(&CurrentTime);

		// If we cannot find the current parent dir specified by this
		// catalog position, or too much time has elapsed since the
		// user last sent in this catalog position, then restart the search
		// from the root dir.  The reason we have a time limitation is that
		// if someone made a CatSearch request N minutes ago, and the
		// current position is deep in the tree, the directory permissions
		// higher up in the tree could have changed by now so that the user
		// shouldn't even have access to this part of the tree anymore.
		// Since we do move up in the tree without rechecking permissions,
		// this could happen.  (We assume that if we got down to the current
		// position in the tree that we had to have had access higher up
		// in order to get here, so moving up is ok.  But if somebody comes
		// back a day later and continues the catsearch where it left off,
		// we shouldn't let them.)  It is too expensive to be rechecking
		// parents' parent permissions everytime we move back up the tree.
		if (((CurrentTime - pCatPosition->cp_TimeStamp) >= MAX_CATSEARCH_TIME) ||
			((pCurParent = AfpFindDfEntryById(pVolDesc,
											  pCatPosition->cp_CurParentId,
											  DFE_DIR)) == NULL))
		{
			// Start over from root directory
			Status = AFP_ERR_CATALOG_CHANGED;
			DBGPRINT(DBG_COMP_AFPAPI_FD, DBG_LEVEL_WARN,
					("AfpCatSearch: Time diff >= MAX_CATSEARCH_TIME or couldn't find CurParent Id!\n"));
			pCurParent = pVolDesc->vds_pDfeRoot;
			Flags = CATFLAGS_SEARCHING_FILES;
			pSep = (PSEP)pResults;
			Status = AFP_ERR_NONE;
			MatchFiles = True;
			MatchDirs = True;
			SizeLeft = Buflen;
			ActCount = 0;
			if (IS_VOLUME_NTFS(pVolDesc))
				CheckAccess = True;
			NewSearch = True;
		}
		else if (pCatPosition->cp_TimeStamp < pVolDesc->vds_ModifiedTime)
		{
			Status = AFP_ERR_CATALOG_CHANGED;
			ASSERT(IS_VOLUME_NTFS(pVolDesc));
			DBGPRINT(DBG_COMP_AFPAPI_FD, DBG_LEVEL_WARN,
					("AfpCatSearch: Catalog timestamp older than IdDb Modtime\n"));
		}

		ASSERT(DFE_IS_DIRECTORY(pCurParent));

		// If we need to resume searching the files for this parent, find the
		// one we should start with, if it is not the first file child.
		if (Flags & CATFLAGS_SEARCHING_FILES)
		{
			//
			// Default is to start with parent's first child which
			// may or may not be null depending on if the parent has had
			// its file children cached in or not.  If we are restarting a
			// search because we had to let go of the IdDb SWMR in order to
			// reaquire for Exclusive access, this parent's children could
			// very well have been cached in by someone else in the mean time.
			// If so then we will pick it up here.
			//
			i = 0;
			pCurFile = pCurParent->dfe_pDirEntry->de_ChildFile[0];

			if (pCatPosition->cp_NextFileId != 0)
			{

				// Find the DFE corresponding to the next fileID to look at
				if (((pCurFile = AfpFindDfEntryById(pVolDesc,
													pCatPosition->cp_NextFileId,
													DFE_FILE)) == NULL) ||
					(pCurFile->dfe_Parent != pCurParent))
				{
					// If we can't find the file that was specified, start over
					// with this parent's first file child and indicate there may
					// be duplicates returned or files missed
					Status = AFP_ERR_CATALOG_CHANGED;
					DBGPRINT(DBG_COMP_AFPAPI_FD, DBG_LEVEL_WARN,
							 ("AfpCatSearch: Could not find file Child ID!\n"));
					i = 0;
					pCurFile = pCurParent->dfe_pDirEntry->de_ChildFile[0];
				}
				else
				{
					i = (pCurFile->dfe_NameHash & (MAX_CHILD_HASH_BUCKETS - 1));
				}
			}
		}
	}

	if (pFDParm1->_fdp_Flags == DFE_FLAGS_FILE_WITH_ID)
		MatchDirs = False;
	else if (pFDParm1->_fdp_Flags == DFE_FLAGS_DIR)
		MatchFiles = False;


	if (NewSearch && MatchDirs)
	{
		SHORT Length;

		ASSERT (DFE_IS_ROOT(pCurParent));

		// See if the volume root itself is a match
		if ((Length = AfpIsCatSearchMatch(pCurParent,
										  Bitmap,
										  DirBitmap,
										  pFDParm1,
										  pFDParm2,
										  pMatchString)) != 0)
		{
			ASSERT(Length <= SizeLeft);
			PUTSHORT2BYTE(&pSep->__Length, Length - sizeof(SEP));
			pSep->__FileDirFlag = FILEDIR_FLAG_DIR;

			afpPackSearchParms(pCurParent,
							   DirBitmap,
							   (PBYTE)pSep + sizeof(SEP));

			pSep = (PSEP)((PBYTE)pSep + Length);
			SizeLeft -= Length;
			ASSERT(SizeLeft >= 0);
			ActCount ++;
		}
	}
	NewSearch = False;

	while (True)
	{
		HaveSeeFiles = HaveSeeFolders = True;

		//
		// First time thru, if we are resuming a search and need to start
		// with the pCurParent's sibling, then do so.
		//
		if (Flags & CATFLAGS_SEARCHING_SIBLING)
		{
			Flags &= ~CATFLAGS_SEARCHING_SIBLING;
			goto check_sibling;
		}

		//
		// If we have not searched this directory yet and this is NTFS, check
		// that user has seefiles/seefolders access in this directory
		//
		if (CheckAccess)
		{
			BYTE		UserRights;
			NTSTATUS	PermStatus;

			ASSERT(IS_VOLUME_NTFS(pVolDesc));
			AfpSetEmptyUnicodeString(&CurPath, 0, NULL);

			// Get the root relative path of this directory
			if (NT_SUCCESS(AfpHostPathFromDFEntry(pCurParent,
												  0,
												  &CurPath)))
			{
				// Check for SeeFiles/SeeFolders which is the most common case
				if (!NT_SUCCESS((PermStatus = AfpCheckParentPermissions(pConnDesc,
																		pCurParent->dfe_AfpId,
																		&CurPath,
																		DIR_ACCESS_READ | DIR_ACCESS_SEARCH,
																		NULL,
																		&UserRights))))
				{
					if (PermStatus == AFP_ERR_ACCESS_DENIED)
					{
						if ((UserRights & DIR_ACCESS_READ) == 0)
							HaveSeeFiles = False;

						if ((UserRights & DIR_ACCESS_SEARCH) == 0)
							HaveSeeFolders = False;
					}
					else
						HaveSeeFiles = HaveSeeFolders = False;
				}

				if (CurPath.Buffer != NULL)
					AfpFreeMemory(CurPath.Buffer);
			}
			else
			{
				HaveSeeFiles = HaveSeeFolders = False;
				DBGPRINT(DBG_COMP_AFPAPI_FD, DBG_LEVEL_ERR,
						("AfpCatSearch: Could not get host path from DFE!!\n"));
			}

			CheckAccess = False;
		}

		// Search the files first if have seefiles access on the current
		// parent and the user has asked for file matches.  If we are
		// resuming a search by looking at a directory child first, don't look
		// at the files.
		if (HaveSeeFiles && MatchFiles && (Flags & CATFLAGS_SEARCHING_FILES))
		{
			PDFENTRY	pDFE;
			SHORT		Length;
			AFPSTATUS	subStatus = AFP_ERR_NONE, subsubStatus = AFP_ERR_NONE;

			if (!DFE_CHILDREN_ARE_PRESENT(pCurParent))
			{
				if (!AfpSwmrLockedExclusive(pSwmr) &&
					!AfpSwmrUpgradeToExclusive(pSwmr))
				{
					if (ActCount > 0)
					{
						// We have at least one thing to return to the user,
						// so return it now and set the flag for next time
						// to take the write lock.
						pNewCatPosition->cp_NextFileId = 0;
						Flags |= CATFLAGS_WRITELOCK_REQUIRED;
						break; // out of while loop
					}
					else
					{
						// Let go of lock and reaquire it for exclusive
						// access.  Start over where we left off here if
						// possible.  Put a new timestamp in the catalog
						// position so if it changes between the time we let
						// go of the lock and reaquire it for exclusive access,
						// we will return AFP_ERR_CATALOG_CHANGED since
						// something could change while we don't own the lock.
						AfpSwmrRelease(pSwmr);
						pCatPosition->cp_Flags = CATFLAGS_WRITELOCK_REQUIRED |
												 CATFLAGS_SEARCHING_FILES;
						pCatPosition->cp_CurParentId = pCurParent->dfe_AfpId;
						pCatPosition->cp_NextFileId = 0;
						AfpGetCurrentTimeInMacFormat(&pCatPosition->cp_TimeStamp);
						DBGPRINT(DBG_COMP_AFPAPI_FD, DBG_LEVEL_INFO,
								("AfpCatSearch: Lock released; reaquiring Exclusive\n"));
						goto CatSearchStart;
					}
				}

				AfpCacheDirectoryTree(pVolDesc,
									  pCurParent,
									  GETFILES,
									  NULL,
									  NULL);
				i = 0;
				pCurFile = pCurParent->dfe_pDirEntry->de_ChildFile[0];

				// If we have the exclusive lock, downgrade it to shared so
				// we don't lock out others who want to read.
				if (AfpSwmrLockedExclusive(pSwmr))
					AfpSwmrDowngradeToShared(pSwmr);
			}

			//
			// Search files for matches.  If we are picking up in the middle
			// of searching the files, then start with the right one as pointed
			// at by pCurFile.
			//
			while (TRUE)
			{
				while (pCurFile == NULL)
				{
					i ++;
					if (i < MAX_CHILD_HASH_BUCKETS)
					{
						pCurFile = pCurParent->dfe_pDirEntry->de_ChildFile[i];
					}
					else
					{
						subsubStatus = STATUS_NO_MORE_FILES;
						break; // out of while (pCurFile == NULL)
					}
				}

				if (subsubStatus != AFP_ERR_NONE)
				{
					break;
				}

				ASSERT(pCurFile->dfe_Parent == pCurParent);

				if ((Length = AfpIsCatSearchMatch(pCurFile,
												  Bitmap,
												  FileBitmap,
												  pFDParm1,
												  pFDParm2,
												  pMatchString)) != 0)
				{
					// Add this to the output buffer if there is room
					if ((Length <= SizeLeft) && (ActCount < *pCount))
					{
						PUTSHORT2BYTE(&pSep->__Length, Length - sizeof(SEP));
						pSep->__FileDirFlag = FILEDIR_FLAG_FILE;

						afpPackSearchParms(pCurFile,
										   FileBitmap,
										   (PBYTE)pSep + sizeof(SEP));

						pSep = (PSEP)((PBYTE)pSep + Length);
						SizeLeft -= Length;
						ASSERT(SizeLeft >= 0);
						ActCount ++;
					}
					else
					{
						// We don't have enough room to return this entry, or
						// we already have found the requested count.  So this
						// will be where we pick up from on the next search
						pNewCatPosition->cp_NextFileId = pCurFile->dfe_AfpId;
						subStatus = STATUS_BUFFER_OVERFLOW;
						break;
					}
				}
                pCurFile = pCurFile->dfe_NextSibling;
			}

			if (subStatus != AFP_ERR_NONE)
			{
				break;	// out of while loop
			}

			Flags = 0;
		}

		// If have seefolders on curparent and curparent has a dir child,
		// Move down the tree to the parent's first directory branch
		if (HaveSeeFolders && (pCurParent->dfe_pDirEntry->de_ChildDir != NULL))
		{
			SHORT Length;

			// If user has asked for directory matches, try the parent's
			// first directory child as a match
			if (MatchDirs &&
				((Length = AfpIsCatSearchMatch(pCurParent->dfe_pDirEntry->de_ChildDir,
											   Bitmap,
											   DirBitmap,
											   pFDParm1,
											   pFDParm2,
											   pMatchString)) != 0))
			{
				// Add this to the output buffer if there is room
				if ((Length <= SizeLeft) && (ActCount < *pCount))
				{
					PUTSHORT2BYTE(&pSep->__Length, Length - sizeof(SEP));
					pSep->__FileDirFlag = FILEDIR_FLAG_DIR;

					afpPackSearchParms(pCurParent->dfe_pDirEntry->de_ChildDir,
									   DirBitmap,
									   (PBYTE)pSep + sizeof(SEP));

					pSep = (PSEP)((PBYTE)pSep + Length);
					SizeLeft -= Length;
					ASSERT(SizeLeft >= 0);
					ActCount ++;
				}
				else
				{
					// We don't have enough room to return this entry, so
					// it will be where we pick up from on the next search
					Flags = CATFLAGS_SEARCHING_DIRCHILD;
					break;
				}
			}

			// Make the current parent's first dir child the new pCurParent
			// and continue the search from there.
			pCurParent = pCurParent->dfe_pDirEntry->de_ChildDir;
			if (IS_VOLUME_NTFS(pVolDesc))
				CheckAccess = True;
			Flags = CATFLAGS_SEARCHING_FILES;
			i = 0;
			pCurFile = pCurParent->dfe_pDirEntry->de_ChildFile[0];
			continue;
		}

		// We either don't have the access rights to go into any directories
		// under this parent, or the current parent did not have any directory
		// children.  See if it has any siblings.  We know we have access to
		// see this level of siblings since we are at this level in the first
		// place.
  check_sibling:
		if (pCurParent->dfe_NextSibling != NULL)
		{
			SHORT 	Length;

			// If user has asked for directory matches, try the parent's
			// next sibling as a match
			if (MatchDirs &&
				((Length = AfpIsCatSearchMatch(pCurParent->dfe_NextSibling,
											   Bitmap,
											   DirBitmap,
											   pFDParm1,
											   pFDParm2,
											   pMatchString)) != 0))
			{
				// Add this to the output buffer if there is room
				if ((Length <= SizeLeft) && (ActCount < *pCount))
				{
					PUTSHORT2BYTE(&pSep->__Length, Length - sizeof(SEP));
					pSep->__FileDirFlag = FILEDIR_FLAG_DIR;

					afpPackSearchParms(pCurParent->dfe_NextSibling,
									   DirBitmap,
									   (PBYTE)pSep + sizeof(SEP));

					pSep = (PSEP)((PBYTE)pSep + Length);
					SizeLeft -= Length;
					ASSERT(SizeLeft >= 0);
					ActCount ++;
				}
				else
				{
					// We don't have enough room to return this entry, so
					// it will be where we pick up from on the next search
					Flags = CATFLAGS_SEARCHING_SIBLING;
					break;
				}
			}

			// Make the current parent's next sibling the new pCurParent and
			// continue the search from there
			pCurParent = pCurParent->dfe_NextSibling;
			if (IS_VOLUME_NTFS(pVolDesc))
				CheckAccess = True;
			Flags = CATFLAGS_SEARCHING_FILES;
			i = 0;
			pCurFile = pCurParent->dfe_pDirEntry->de_ChildFile[0];
			continue;
		}

		// When we hit the root directory again we have searched everything.
		if (DFE_IS_ROOT(pCurParent))
		{
			Status = AFP_ERR_EOF;
			break;
		}

		// Move back up the tree and see if the parent has a sibling to
		// traverse.  If not, then it will come back here and move up
		// the tree again till it finds a node with a sibling or hits
		// the root.
		pCurParent = pCurParent->dfe_Parent;
		goto check_sibling;
	}

	if ((Status == AFP_ERR_NONE) || (Status == AFP_ERR_CATALOG_CHANGED) ||
		(Status == AFP_ERR_EOF))
	{
		// return the current catalog position and number of items returned
		if (Status != AFP_ERR_EOF)
		{
			ASSERT(Flags != 0);
			ASSERT(ActCount > 0);
			pNewCatPosition->cp_Flags = Flags;
			pNewCatPosition->cp_CurParentId = pCurParent->dfe_AfpId;
			AfpGetCurrentTimeInMacFormat(&pNewCatPosition->cp_TimeStamp);
		}
		*pCount = ActCount;
		ASSERT(SizeLeft >= 0);
		*pSizeLeft = SizeLeft;
	}

	AfpSwmrRelease(pSwmr);

	return Status;
}


/***	afpPackSearchParms
 *
 *
 * 	LOCKS_ASSUMED: vds_IdDbAccessLock (Shared or Exclusive)
 */
VOID
afpPackSearchParms(
	IN	PDFENTRY	pDfe,
	IN	DWORD		Bitmap,
	IN	PBYTE		pBuf
)
{
	DWORD		Offset = 0;
	ANSI_STRING	AName;
	BYTE		NameBuf[AFP_LONGNAME_LEN+1];

	PAGED_CODE( );

	if (Bitmap & FD_BITMAP_PARENT_DIRID)
	{
		PUTDWORD2DWORD(pBuf, pDfe->dfe_Parent->dfe_AfpId);
		Offset += sizeof(DWORD);
	}
	if (Bitmap & FD_BITMAP_LONGNAME)
	{
		PUTDWORD2SHORT(pBuf + Offset, Offset + sizeof(USHORT));
		Offset += sizeof(USHORT);
		PUTSHORT2BYTE(pBuf + Offset, pDfe->dfe_UnicodeName.Length/sizeof(WCHAR));
		AfpInitAnsiStringWithNonNullTerm(&AName, sizeof(NameBuf), NameBuf);
		AfpConvertMungedUnicodeToAnsi(&pDfe->dfe_UnicodeName,
									  &AName);
		RtlCopyMemory(pBuf + Offset + sizeof(BYTE),
					  NameBuf,
					  AName.Length);
		Offset += sizeof(BYTE) + pDfe->dfe_UnicodeName.Length/sizeof(WCHAR);
	}

	if (Offset & 1)
		*(pBuf + Offset) = 0;
}


/***	AfpSetDFFileFlags
 *
 *	Set or clear the DAlreadyOpen or RAlreadyOpen flags for a DFEntry of type
 *	File, or mark the file as having a FileId assigned.
 *
 *	LOCKS: vds_idDbAccessLock (SWMR, Exclusive)
 */
AFPSTATUS
AfpSetDFFileFlags(
	IN	PVOLDESC		pVolDesc,
	IN	DWORD			AfpId,
	IN	DWORD			Flags		OPTIONAL,
	IN	BOOLEAN			SetFileId,
	IN	BOOLEAN			ClrFileId
)
{
	PDFENTRY		pDfeFile;
	AFPSTATUS		Status = AFP_ERR_NONE;

	PAGED_CODE( );

	ASSERT(!(SetFileId | ClrFileId) || (SetFileId ^ ClrFileId));

	AfpSwmrAcquireExclusive(&pVolDesc->vds_IdDbAccessLock);

	pDfeFile = AfpFindDfEntryById(pVolDesc, AfpId, DFE_FILE);
	if (pDfeFile != NULL)
	{
#ifdef	AGE_DFES
		if (IS_VOLUME_AGING_DFES(pVolDesc))
		{
			if (Flags)
			{
				pDfeFile->dfe_Parent->dfe_pDirEntry->de_ChildForkOpenCount ++;
			}
		}
#endif
		pDfeFile->dfe_Flags |=  (Flags & DFE_FLAGS_OPEN_BITS);
		if (SetFileId)
		{
			if (DFE_IS_FILE_WITH_ID(pDfeFile))
				Status = AFP_ERR_ID_EXISTS;
			DFE_SET_FILE_ID(pDfeFile);
		}
		if (ClrFileId)
		{
			if (!DFE_IS_FILE_WITH_ID(pDfeFile))
				Status = AFP_ERR_ID_NOT_FOUND;
			DFE_CLR_FILE_ID(pDfeFile);
		}
	}
	else Status = AFP_ERR_OBJECT_NOT_FOUND;

	AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock);
	return Status;
}


/***	AfpCacheParentModTime
 *
 *	When the contents of a directory change, the parent LastMod time must be
 *  updated.  Since we don't want to wait for a notification of this,
 *  certain apis must go query for the new parent mod time and cache it.
 *  These include:  CreateDir, CreateFile, CopyFile (Dest), Delete,
 *  Move (Src & Dest), Rename and ExchangeFiles.
 *
 *  LOCKS_ASSUMED: vds_IdDbAccessLock (SWMR, Exclusive)
 */
VOID
AfpCacheParentModTime(
	IN	PVOLDESC		pVolDesc,
	IN	PFILESYSHANDLE	pHandle		OPTIONAL,	// if pPath not supplied
	IN	PUNICODE_STRING	pPath		OPTIONAL,	// if pHandle not supplied
	IN	PDFENTRY		pDfeParent	OPTIONAL,	// if ParentId not supplied
	IN	DWORD			ParentId	OPTIONAL 	// if pDfeParent not supplied
)
{
	FILESYSHANDLE	fshParent;
	PFILESYSHANDLE 	phParent;
	NTSTATUS		Status;

	PAGED_CODE( );

	ASSERT(AfpSwmrLockedExclusive(&pVolDesc->vds_IdDbAccessLock));

	if (!ARGUMENT_PRESENT(pDfeParent))
	{
		ASSERT(ARGUMENT_PRESENT(ParentId));
		pDfeParent = AfpFindDfEntryById(pVolDesc, ParentId, DFE_DIR);
		if (pDfeParent == NULL)
		{
			return;
		}
	}

	if (!ARGUMENT_PRESENT(pHandle))
	{
		ASSERT(ARGUMENT_PRESENT(pPath));
		Status = AfpIoOpen(&pVolDesc->vds_hRootDir,
							AFP_STREAM_DATA,
							FILEIO_OPEN_DIR,
							pPath,
							FILEIO_ACCESS_NONE,
							FILEIO_DENY_NONE,
							False,
							&fshParent);
		if (!NT_SUCCESS(Status))
		{
			return;
		}
		phParent = &fshParent;
	}
	else
	{
		ASSERT(pHandle->fsh_FileHandle != NULL);
		phParent = pHandle;
	}

	AfpIoQueryTimesnAttr(phParent,
						 NULL,
						 &pDfeParent->dfe_LastModTime,
						 NULL);
	if (!ARGUMENT_PRESENT(pHandle))
	{
		AfpIoClose(&fshParent);
	}
}


/***	afpAllocDfe
 *
 *	Allocate a DFE from the DFE Blocks. The DFEs are allocated in 4K chunks and internally
 *	managed. The idea is primarily to reduce the number of faults we may take during
 *	enumeration/pathmap code in faulting in multiple pages to get multiple DFEs.
 *
 *	The DFEs are allocated out of paged memory.
 *
 *	It is important to keep blocks which are all used up at the end, so that if we hit a
 *	block which is empty, we can stop.
 *
 *	LOCKS:	afpDfeBlockLock (SWMR, Exclusive)
 */
LOCAL PDFENTRY FASTCALL
afpAllocDfe(
	IN	LONG	Index,
	IN	BOOLEAN	fDir
)
{
	PDFEBLOCK	pDfb;
	PDFENTRY	pDfEntry = NULL;
#ifdef	PROFILING
	TIME		TimeS, TimeE, TimeD;

	INTERLOCKED_INCREMENT_LONG(&AfpServerProfile->perf_DFEAllocCount);
	AfpGetPerfCounter(&TimeS);
#endif

	PAGED_CODE( );

	ASSERT ((Index >= 0) && (Index < MAX_BLOCK_TYPE));

	AfpSwmrAcquireExclusive(&afpDfeBlockLock);

	// If the block head has no free entries then there are none !!
	// Pick the right block based on whether it is file or dir
	pDfb = fDir ? afpDirDfeBlockHead[Index] : afpFileDfeBlockHead[Index];

	if (pDfb != NULL)
	{
		ASSERT(VALID_DFB(pDfb));
		ASSERT((fDir && (pDfb->dfb_NumFree <= afpDfeNumDirBlocks[Index])) ||
			   (!fDir && (pDfb->dfb_NumFree <= afpDfeNumFileBlocks[Index])));

		if (pDfb->dfb_NumFree == 0)
		{
			DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
					("afpAllocDfe: Hit an empty block, aborting search ...\n"));
			pDfb = NULL;
		}
		else
		{
			DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
					("afpAllocDfe: Found space in Block %lx\n", pDfb));
		}
	}

	if (pDfb == NULL)
	{
		DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
				("afpAllocDfe: ... and allocating a new block for index %ld\n", Index));

		ASSERT(QUAD_SIZED(sizeof(DFEBLOCK)));
		ASSERT(QUAD_SIZED(sizeof(DIRENTRY)));
		ASSERT(QUAD_SIZED(sizeof(DFENTRY)));

		if ((pDfb = (PDFEBLOCK)AfpAllocPAPagedMemory(BLOCK_ALLOC_SIZE)) != NULL)
		{
			LONG	i;
			USHORT	DfeSize, UnicodeSize, MaxDfes, DirEntrySize;

#if	DBG
			afpDfbAllocCount ++;
#endif
			UnicodeSize = afpDfeUnicodeBufSize[Index];

			DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_WARN,
					("afpAllocDfe: Allocated a new block for index %d\n", Index));

			// Link it in the list
			if (fDir)
			{
				AfpLinkDoubleAtHead(afpDirDfeBlockHead[Index],
									pDfb,
									dfb_Next,
									dfb_Prev);
				DfeSize = afpDfeDirBlockSize[Index];
				pDfb->dfb_NumFree = MaxDfes = afpDfeNumDirBlocks[Index];
				DirEntrySize = sizeof(DIRENTRY);
			}
			else
			{
				AfpLinkDoubleAtHead(afpFileDfeBlockHead[Index],
									pDfb,
									dfb_Next,
									dfb_Prev);
				DfeSize = afpDfeFileBlockSize[Index];
				pDfb->dfb_NumFree = MaxDfes = afpDfeNumFileBlocks[Index];
				DirEntrySize = 0;
			}

			ASSERT(QUAD_SIZED(DfeSize));
			pDfb->dfb_fDir = fDir;

			// Initialize the list of free dfentries
			for (i = 0,
					pDfEntry =
					pDfb->dfb_FreeHead = (PDFENTRY)((PBYTE)pDfb + sizeof(DFEBLOCK));
				 i < MaxDfes;
				 i++, pDfEntry = pDfEntry->dfe_NextFree)
			{
				pDfEntry->dfe_NextFree = (i == (MaxDfes - 1)) ?
											NULL :
											(PDFENTRY)((PBYTE)pDfEntry + DfeSize);
				pDfEntry->dfe_pDirEntry = NULL;
				if (fDir)
					pDfEntry->dfe_pDirEntry = (PDIRENTRY)((PCHAR)pDfEntry+sizeof(DFENTRY));
				pDfEntry->dfe_UnicodeName.Buffer = (PWCHAR)((PCHAR)pDfEntry+
															DirEntrySize+
															sizeof(DFENTRY));
				pDfEntry->dfe_UnicodeName.MaximumLength = UnicodeSize;
			}
		}
	}

	if (pDfb != NULL)
	{
		PDFEBLOCK	pTmp;

		ASSERT(VALID_DFB(pDfb));

		pDfEntry = pDfb->dfb_FreeHead;
		ASSERT(VALID_DFE(pDfEntry));
#if	DBG
		afpDfeAllocCount ++;
#endif
		pDfb->dfb_FreeHead = pDfEntry->dfe_NextFree;
		pDfb->dfb_Age = 0;
		pDfb->dfb_NumFree --;

		// If the block is now empty, unlink it from here and move it
		// to the first empty slot. We know that all blocks 'earlier' than
		// this are non-empty.
		if ((pDfb->dfb_NumFree == 0) &&
			((pTmp = pDfb->dfb_Next) != NULL) &&
			(pTmp->dfb_NumFree > 0))
		{
			AfpUnlinkDouble(pDfb, dfb_Next, dfb_Prev);
			for (; pTmp != NULL; pTmp = pTmp->dfb_Next)
			{
				if (pTmp->dfb_NumFree == 0)
				{
					// Found a free one. Park it right here.
					AfpInsertDoubleBefore(pDfb, pTmp, dfb_Next, dfb_Prev);
					break;
				}
				else if (pTmp->dfb_Next == NULL)	// We reached the end
				{
					AfpLinkDoubleAtEnd(pDfb, pTmp, dfb_Next, dfb_Prev);
					break;
				}
			}
		}

		pDfEntry->dfe_UnicodeName.Length = 0;
	}

	AfpSwmrRelease(&afpDfeBlockLock);

#ifdef	PROFILING
	AfpGetPerfCounter(&TimeE);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;
	INTERLOCKED_ADD_LARGE_INTGR(&AfpServerProfile->perf_DFEAllocTime,
								 TimeD,
								 &AfpStatisticsLock);
#endif
	if ((pDfEntry != NULL) &&
		(pDfEntry->dfe_pDirEntry != NULL))
	{
		// For a directory ZERO out the directory entry
		RtlZeroMemory(&pDfEntry->dfe_pDirEntry->de_ChildDir, sizeof(DIRENTRY));
	}

	return pDfEntry;
}


/***	afpFreeDfe
 *
 *	Return a DFE to the allocation block.
 *
 *	LOCKS:	afpDfeBlockLock (SWMR, Exclusive)
 */
LOCAL VOID FASTCALL
afpFreeDfe(
	IN	PDFENTRY	pDfEntry
)
{
	PDFEBLOCK	pDfb;
	USHORT		NumBlks, index;
#ifdef	PROFILING
	TIME			TimeS, TimeE, TimeD;

	INTERLOCKED_INCREMENT_LONG(&AfpServerProfile->perf_DFEFreeCount);
	AfpGetPerfCounter(&TimeS);
#endif

	PAGED_CODE( );

	// NOTE: The following code *depends* on the fact that we allocate DFBs as
	//		 single page blocks and also that these are allocated *at* page boundaries
	//		 This lets us *cheaply* get to the owning DFB from the DFE.
	pDfb = (PDFEBLOCK)((DWORD)pDfEntry & ~(PAGE_SIZE-1));
	ASSERT(VALID_DFB(pDfb));

	AfpSwmrAcquireExclusive(&afpDfeBlockLock);

#if	DBG
	afpDfeAllocCount --;
#endif

	index = USIZE_TO_INDEX(pDfEntry->dfe_UnicodeName.MaximumLength);
	NumBlks = (pDfb->dfb_fDir) ? afpDfeNumDirBlocks[index] : afpDfeNumFileBlocks[index];

	ASSERT((pDfb->dfb_fDir && (pDfb->dfb_NumFree < NumBlks)) ||
		   (!pDfb->dfb_fDir && (pDfb->dfb_NumFree < NumBlks)));

	DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
			("AfpFreeDfe: Returning pDfEntry %lx to Block %lx, size %d\n",
			pDfEntry, pDfb, pDfEntry->dfe_UnicodeName.MaximumLength));
	pDfb->dfb_NumFree ++;
	pDfEntry->dfe_NextFree = pDfb->dfb_FreeHead;
	pDfb->dfb_FreeHead = pDfEntry;

	if (AfpServerState == AFP_STATE_STOP_PENDING)
	{
		if (pDfb->dfb_NumFree == NumBlks)
		{
			DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_WARN,
					("afpFreeDfe: Freeing Block %lx\n", pDfb));
			AfpUnlinkDouble(pDfb, dfb_Next, dfb_Prev);
			AfpFreePAPagedMemory(pDfb, BLOCK_ALLOC_SIZE);
#if	DBG
			afpDfbAllocCount --;
#endif
		}
	}

	else if (pDfb->dfb_NumFree == 1)
	{
		// If this block's status is changing from a 'none available' to 'available'
		// move him to the head of the list.
		LONG		Index;

		Index = USIZE_TO_INDEX(pDfEntry->dfe_UnicodeName.MaximumLength);
		AfpUnlinkDouble(pDfb, dfb_Next, dfb_Prev);
		if (pDfb->dfb_fDir)
		{
			AfpLinkDoubleAtHead(afpDirDfeBlockHead[Index],
								pDfb,
								dfb_Next,
								dfb_Prev);
		}
		else
		{
			AfpLinkDoubleAtHead(afpFileDfeBlockHead[Index],
								pDfb,
								dfb_Next,
								dfb_Prev);
		}
	}

	AfpSwmrRelease(&afpDfeBlockLock);
#ifdef	PROFILING
	AfpGetPerfCounter(&TimeE);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;
	INTERLOCKED_ADD_LARGE_INTGR(&AfpServerProfile->perf_DFEFreeTime,
								 TimeD,
								 &AfpStatisticsLock);
#endif
}


/***	afpDfeBlockAge
 *
 *	Age out Dfe Blocks
 *
 *	LOCKS:	afpDfeBlockLock (SWMR, Exclusive)
 */
AFPSTATUS FASTCALL
afpDfeBlockAge(
	IN	PPDFEBLOCK	ppBlockHead
)
{
	int			index, MaxDfes;
	PDFEBLOCK	pDfb;

	PAGED_CODE( );

	AfpSwmrAcquireExclusive(&afpDfeBlockLock);

	for (index = 0; index < MAX_BLOCK_TYPE; index++)
	{
		pDfb = ppBlockHead[index];
		if (pDfb != NULL)
		{
			MaxDfes = pDfb->dfb_fDir ?
						afpDfeNumDirBlocks[index] : afpDfeNumFileBlocks[index];
		}

		while (pDfb != NULL)
		{
			PDFEBLOCK	pFree;

			ASSERT(VALID_DFB(pDfb));

			pFree = pDfb;
			pDfb = pDfb->dfb_Next;

			// Since all blocks which are completely used up are at the tail end of
			// the list, if we encounter one, we are done.
			if (pFree->dfb_NumFree == 0)
				break;

			if (pFree->dfb_NumFree == MaxDfes)
			{
				DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_WARN,
						("afpDfeBlockAge: Aging Block %lx, Size %d\n", pFree,
						pFree->dfb_fDir ? afpDfeDirBlockSize[index] : afpDfeFileBlockSize[index]));
				if (++(pFree->dfb_Age) >= MAX_BLOCK_AGE)
				{
#ifdef	PROFILING
					INTERLOCKED_INCREMENT_LONG( &AfpServerProfile->perf_DFEAgeCount);
#endif
					DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_WARN,
							("afpDfeBlockAge: Freeing Block %lx, Size %d\n", pFree,
							pDfb->dfb_fDir ? afpDfeDirBlockSize[index] : afpDfeFileBlockSize[index]));
					AfpUnlinkDouble(pFree, dfb_Next, dfb_Prev);
					AfpFreePAPagedMemory(pFree, BLOCK_ALLOC_SIZE);
#if	DBG
					afpDfbAllocCount --;
#endif
				}
			}
		}
	}

	AfpSwmrRelease(&afpDfeBlockLock);

	return AFP_ERR_REQUEUE;
}


/***	AfpInitIdDb
 *
 *	This routine initializes the memory image (and all related volume descriptor
 *	fields) of the ID index database for a new volume.  The entire tree is
 *  scanned so all the file/dir cached info can be read in and our view of
 *  the volume tree will be complete.  If an index database already exists
 *  on the disk for the volume root directory, that stream is read in. If this
 *	is a newly created volume, the Afp_IdIndex stream is created on the root of
 *	the volume.  If this is a CDFS volume, only the memory image is initialized.
 *
 *	The IdDb is not locked since the volume is still 'in transition' and not
 *	accessed by anybody.
 */
NTSTATUS FASTCALL
AfpInitIdDb(
	IN	PVOLDESC pVolDesc
)
{
	NTSTATUS		Status;
	ULONG			CreateInfo;
	FILESYSHANDLE	fshIdDb;

	PAGED_CODE( );

	DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
			("AfpInitIdDb: Initializing Id Database...\n"));

	do
	{
		afpInitializeIdDb(pVolDesc);

		// if this is not a CDFS volume, attempt to create the ID DB header
		// stream.  If it already exists, open it and read it in.
		if (IS_VOLUME_NTFS(pVolDesc))
		{
			// Force the scavenger to write out the IdDb and header when the
			// volume is successfully added
			pVolDesc->vds_Flags |= VOLUME_IDDBHDR_DIRTY;

			Status = AfpIoCreate(&pVolDesc->vds_hRootDir,
								AFP_STREAM_IDDB,
								&UNullString,
								FILEIO_ACCESS_READWRITE,
								FILEIO_DENY_WRITE,
								FILEIO_OPEN_FILE_SEQ,
								FILEIO_CREATE_INTERNAL,
								FILE_ATTRIBUTE_NORMAL,
								False,
								NULL,
								&fshIdDb,
								&CreateInfo,
								NULL,
								NULL,
								NULL);

			if (!NT_SUCCESS(Status))
			{
				break;
			}

			if (CreateInfo == FILE_OPENED)
			{
				// read in the existing header. If we fail, just start from scratch
				Status = afpReadIdDb(pVolDesc, &fshIdDb);
				if (!NT_SUCCESS(Status) || (pVolDesc->vds_pDfeRoot == NULL))
					CreateInfo = FILE_CREATED;
			}

			if (CreateInfo == FILE_CREATED)
			{
				// add the root and parent of root to the idindex
				// and initialize a new header
				Status = afpSeedIdDb(pVolDesc);
			}
			else if (CreateInfo != FILE_OPENED)
			{
				DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_WARN,
						("AfpInitIdDb: unexpected create action 0x%lx\n", CreateInfo));
				ASSERTMSG("Unexpected Create Action\n", 0); // this should never happen
				Status = STATUS_UNSUCCESSFUL;
			}

			AfpIoClose(&fshIdDb);
		}
		else
		{
			// its CDFS, just initialize the memory image of the IdDB
			Status = afpSeedIdDb(pVolDesc);
		}

		if (!NT_SUCCESS(Status))
		{
			break;
		}
	} while (False);

	if (!NT_SUCCESS(Status))
	{
		AFPLOG_ERROR(AFPSRVMSG_INIT_IDDB,
					 Status,
					 NULL,
					 0,
					 &pVolDesc->vds_Name);
		Status = STATUS_UNSUCCESSFUL;
	}

	return Status;
}


/***	afpSeedIdDb
 *
 *	This routine adds the 'parent of root' and the root directory entries
 *	to a newly created ID index database (the memory image of the iddb).
 *
**/
LOCAL NTSTATUS FASTCALL
afpSeedIdDb(
	IN	PVOLDESC pVolDesc
)
{
	PDFENTRY		pDfEntry;
	AFPTIME			CurrentTime;
	AFPINFO			afpinfo;
	FILESYSHANDLE	fshAfpInfo, fshComment, fshData;
	DWORD			i, crinfo, Attr;
	FINDERINFO		FinderInfo;
	NTSTATUS		Status = STATUS_SUCCESS;

	PAGED_CODE( );

	DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
			("afpSeedIdDb: Creating new Id Database...\n"));

	do
	{
		pDfEntry = AfpFindDfEntryById(pVolDesc,
									  AFP_ID_PARENT_OF_ROOT,
									  DFE_DIR);
		ASSERT (pDfEntry != NULL);

		// add the root directory to the id index
		if ((pDfEntry = AfpAddDfEntry(pVolDesc,
									  pDfEntry,
									  &pVolDesc->vds_Name,
									  True,
									  AFP_ID_ROOT)) == NULL )
		{
			Status = STATUS_NO_MEMORY;
			break;
		}
		pVolDesc->vds_pDfeRoot = pDfEntry;	// Initialize pointer to root.

		// Attempt to open the comment stream. If it succeeds, set a flag in
		// the DFE indicating that this thing does indeed have a comment.
		if (NT_SUCCESS(AfpIoOpen(&pVolDesc->vds_hRootDir,
								 AFP_STREAM_COMM,
								 FILEIO_OPEN_FILE,
								 &UNullString,
								 FILEIO_ACCESS_NONE,
								 FILEIO_DENY_NONE,
								 False,
								 &fshComment)))
		{
			DFE_SET_COMMENT(pDfEntry);
			AfpIoClose(&fshComment);
		}

		// Get the directory information for volume root dir. Do not get the
		// mod-time. See below.
		Status = AfpIoQueryTimesnAttr(&pVolDesc->vds_hRootDir,
									  &pDfEntry->dfe_CreateTime,
									  NULL,
									  &Attr);
		// Setup up root directories Last ModTime such that it will
		// get enumerated.
        AfpConvertTimeFromMacFormat(BEGINNING_OF_TIME,
									&pDfEntry->dfe_LastModTime);

		ASSERT(NT_SUCCESS(Status));

		pDfEntry->dfe_NtAttr = (USHORT)Attr & FILE_ATTRIBUTE_VALID_FLAGS;

		if (IS_VOLUME_NTFS(pVolDesc))
		{
			if (NT_SUCCESS(Status = AfpCreateAfpInfo(&pVolDesc->vds_hRootDir,
													 &fshAfpInfo,
													 &crinfo)))
			{
				if ((crinfo == FILE_CREATED) ||
					(!NT_SUCCESS(AfpReadAfpInfo(&fshAfpInfo, &afpinfo))))
				{
					Status = AfpSlapOnAfpInfoStream(NULL,
													NULL,
													&pVolDesc->vds_hRootDir,
													&fshAfpInfo,
													AFP_ID_ROOT,
													True,
													NULL,
													&afpinfo);
				}
				else
				{
					// Just make sure the afp ID is ok, preserve the rest
					if (afpinfo.afpi_Id != AFP_ID_ROOT)
					{
						afpinfo.afpi_Id = AFP_ID_ROOT;
						AfpWriteAfpInfo(&fshAfpInfo, &afpinfo);
					}
				}
				AfpIoClose(&fshAfpInfo);

				pDfEntry->dfe_AfpAttr = afpinfo.afpi_Attributes;
				pDfEntry->dfe_FinderInfo = afpinfo.afpi_FinderInfo;
				if (pVolDesc->vds_Flags & AFP_VOLUME_HAS_CUSTOM_ICON)
				{
					// Don't bother writing back to disk since we do not
					// try to keep this in sync in the permanent afpinfo
					// stream with the actual existence of the icon<0d> file.
					pDfEntry->dfe_FinderInfo.fd_Attr1 |= FINDER_FLAG_HAS_CUSTOM_ICON;
				}
				pDfEntry->dfe_BackupTime = afpinfo.afpi_BackupTime;
				DFE_OWNER_ACCESS(pDfEntry) = afpinfo.afpi_AccessOwner;
				DFE_GROUP_ACCESS(pDfEntry) = afpinfo.afpi_AccessGroup;
				DFE_WORLD_ACCESS(pDfEntry) = afpinfo.afpi_AccessWorld;
			}
		}
		else // CDFS
		{
			RtlZeroMemory(&pDfEntry->dfe_FinderInfo, sizeof(FINDERINFO));

			if (IS_VOLUME_CD_HFS(pVolDesc))
			{
				Status = AfpIoOpen(&pVolDesc->vds_hRootDir,
							 AFP_STREAM_DATA,
							 FILEIO_OPEN_DIR,
							 &UNullString,
							 FILEIO_ACCESS_NONE,
							 FILEIO_DENY_NONE,
							 False,
							 &fshData);
				if (!NT_SUCCESS(Status))
				{
				    DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_ERR,
					  ("afpSeedIdDb: AfpIoOpeno failed with %lx for CD_HFS\n", Status));
				    break;
				}
				if (NT_SUCCESS(Status = AfpReadCdHfsAfpInfo(&fshData,
								(PHFS_INFORMATION_BUFFER)&FinderInfo)))
				{
					pDfEntry->dfe_FinderInfo = FinderInfo;
					if (pVolDesc->vds_Flags & AFP_VOLUME_HAS_CUSTOM_ICON)
					{
					    pDfEntry->dfe_FinderInfo.fd_Attr1 |= FINDER_FLAG_HAS_CUSTOM_ICON;
					}
				}
				else
				{
					DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_ERR,
						  ("afpSeedIdDb: AfpReadCdHfsInfo failed with %lx for CD_HFS\n", Status));
				}

				AfpIoClose(&fshData);
			}
			pDfEntry->dfe_BackupTime = BEGINNING_OF_TIME;
			DFE_OWNER_ACCESS(pDfEntry) = (DIR_ACCESS_SEARCH | DIR_ACCESS_READ);
			DFE_GROUP_ACCESS(pDfEntry) = (DIR_ACCESS_SEARCH | DIR_ACCESS_READ);
			DFE_WORLD_ACCESS(pDfEntry) = (DIR_ACCESS_SEARCH | DIR_ACCESS_READ);
			pDfEntry->dfe_AfpAttr = 0;
		}
	} while (False);

	return Status;
}


/***	AfpFreeIdIndexTables
 *
 *	Free the allocated memory for the volume id index tables. The volume is
 *	about to be deleted. Ensure that either the volume is readonly or it is
 *	clean i.e. the scavenger threads have written it back.
 *
 */
VOID FASTCALL
AfpFreeIdIndexTables(
	IN	PVOLDESC pVolDesc
)
{
	LONG	i;

	PAGED_CODE( );

	ASSERT (IS_VOLUME_RO(pVolDesc) ||
			(pVolDesc->vds_pOpenForkDesc == NULL));

	// Traverse each of the hashed indices and free the entries.
	// Need only traverse the overflow links. Ignore other links.
	// JH - Do not bother if we are here during shutdown
	if (AfpServerState != AFP_STATE_SHUTTINGDOWN)
	{
		PDFENTRY pDfEntry, pFree;

		for (i = 0; i < IDINDEX_FILE_BUCKETS; i++)
		{
			for (pDfEntry = pVolDesc->vds_pDfeFileBuckets[i];
				 pDfEntry != NULL;
				 NOTHING)
			{
				ASSERT(VALID_DFE(pDfEntry));

				pFree = pDfEntry;
				pDfEntry = pDfEntry->dfe_NextOverflow;
				FREE_DFE(pFree);
			}
			pVolDesc->vds_pDfeFileBuckets[i] = NULL;
		}
		for (i = 0; i < IDINDEX_DIR_BUCKETS; i++)
		{
			for (pDfEntry = pVolDesc->vds_pDfeDirBuckets[i];
				 pDfEntry != NULL;
				 NOTHING)
			{
				ASSERT(VALID_DFE(pDfEntry));

				pFree = pDfEntry;
				pDfEntry = pDfEntry->dfe_NextOverflow;
				FREE_DFE(pFree);
			}
			pVolDesc->vds_pDfeDirBuckets[i] = NULL;
		}
		RtlZeroMemory(pVolDesc->vds_pDfeCache,
					  IDINDEX_CACHE_ENTRIES * sizeof(PDFENTRY));
	}
}


/***	afpRenameInvalidWin32Name
 *
 */
VOID
afpRenameInvalidWin32Name(
	IN	PFILESYSHANDLE		phRootDir,
	IN	BOOLEAN				IsDir,
	IN	PUNICODE_STRING		pName
)
{
	FILESYSHANDLE	Fsh;
	NTSTATUS		rc;
	WCHAR			wc;

	DBGPRINT(DBG_COMP_CHGNOTIFY, DBG_LEVEL_ERR,
			("afpRenameInvalidWin32Name: renaming on the fly %Z\n", pName));

	// Rename it now
	if (NT_SUCCESS(AfpIoOpen(phRootDir,
							 AFP_STREAM_DATA,
							 IsDir ? FILEIO_OPEN_DIR : FILEIO_OPEN_FILE,
							 pName,
							 FILEIO_ACCESS_DELETE,
							 FILEIO_DENY_NONE,
							 False,
							 &Fsh)))
	{
		DWORD	NtAttr;

		// Before we attempt a rename, check if the RO bit is set. If it is
		// reset it temporarily.
		rc = AfpIoQueryTimesnAttr(&Fsh, NULL, NULL, &NtAttr);
		ASSERT(NT_SUCCESS(rc));

		if (NtAttr & FILE_ATTRIBUTE_READONLY)
		{
			rc = AfpIoSetTimesnAttr(&Fsh,
									NULL,
									NULL,
									0,
									FILE_ATTRIBUTE_READONLY,
									NULL,
									NULL);
			ASSERT(NT_SUCCESS(rc));
		}

		// Convert the name back to UNICODE so that munging happens !!!
		wc = pName->Buffer[(pName->Length - 1)/sizeof(WCHAR)];
		if (wc == UNICODE_SPACE)
			pName->Buffer[(pName->Length - 1)/sizeof(WCHAR)] = AfpMungedUnicodeSpace;
		if (wc == UNICODE_PERIOD)
			pName->Buffer[(pName->Length - 1)/sizeof(WCHAR)] = AfpMungedUnicodePeriod;

		rc = AfpIoMoveAndOrRename(&Fsh,
								  NULL,
								  pName,
								  NULL,
								  NULL,
								  NULL,
								  NULL,
								  NULL);
		ASSERT(NT_SUCCESS(rc));

		// Set the RO Attr back if it was set to begin with
		if (NtAttr & FILE_ATTRIBUTE_READONLY)
		{
			rc = AfpIoSetTimesnAttr(&Fsh,
									NULL,
									NULL,
									FILE_ATTRIBUTE_READONLY,
									0,
									NULL,
									NULL);
			ASSERT(NT_SUCCESS(rc));
		}

		AfpIoClose(&Fsh);
	}
}


#ifdef	AGE_DFES

/***	AfpAgeDfEntries
 *
 *	Age out DfEntries out of the Id database. The Files in directories which have not been
 *	accessed for VOLUME_IDDB_AGE_DELAY are aged out. The directories are marked so that
 *	they will be enumerated when they are hit next.
 *
 *	LOCKS:	vds_idDbAccessLock(SWMR, Exclusive)
 */
VOID FASTCALL
AfpAgeDfEntries(
	IN	PVOLDESC	pVolDesc
)
{
	PDFENTRY	pDfEntry, *Stack = NULL;
	LONG		i, StackPtr = 0;
	AFPTIME		Now;

	ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);

	AfpGetCurrentTimeInMacFormat(&Now);
	AfpSwmrAcquireExclusive(&pVolDesc->vds_IdDbAccessLock);

	// Potentially all of the files can be aged out. Allocate 'stack' space
	// for all of the directory DFEs
	if ((Stack = (PDFENTRY *)
				AfpAllocNonPagedMemory(pVolDesc->vds_NumDirDfEntries*sizeof(PDFENTRY))) != NULL)
	{
		// 'Prime' the stack of Dfe's
		Stack[StackPtr++] = pVolDesc->vds_pDfeRoot;

		while (StackPtr > 0)
		{
			PDFENTRY	pDir;

			pDfEntry = Stack[--StackPtr];

			ASSERT(DFE_IS_DIRECTORY(pDfEntry));
			if ((pDfEntry->dfe_AfpId >= AFP_FIRST_DIRID) &&
				(pDfEntry->dfe_pDirEntry->de_ChildForkOpenCount == 0)	 &&
				((Now - pDfEntry->dfe_pDirEntry->de_LastAccessTime) > VOLUME_IDDB_AGE_DELAY))
			{
				PDFENTRY	pFile, pNext;

				DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
						("AfpAgeDfEntries: Aging out directory %Z\n", &pDfEntry->dfe_UnicodeName));
				// This directory's files need to be nuked
				pDfEntry->dfe_FileOffspring = 0;
				pDfEntry->dfe_Flags &= ~DFE_FLAGS_FILES_CACHED;

				for (i = 0; i < MAX_CHILD_HASH_BUCKETS; i++)
				{
					for (pFile = pDfEntry->dfe_pDirEntry->de_ChildFile[i];
						 pFile != NULL;
						 pFile = pNext)
					{
						pNext = pFile->dfe_NextSibling;

						// Unlink it from the hash buckets
						AfpUnlinkDouble(pFile,
										dfe_NextOverflow,
										dfe_PrevOverflow);
						// Nuke it from the cache if it is there
						if (pVolDesc->vds_pDfeCache[HASH_CACHE_ID(pFile->dfe_AfpId)] == pFile)
						{
							pVolDesc->vds_pDfeCache[HASH_CACHE_ID(pFile->dfe_AfpId)] = NULL;
						}
						// Finally free it
						FREE_DFE(pFile);
					}
					pDfEntry->dfe_pDirEntry->de_ChildFile[i] = NULL;
				}
			}

#if 0
			// NOTE: Should we leave the tree under 'Network Trash Folder' alone ?
			if (pDfEntry->dfe_AfpId == AFP_ID_NETWORK_TRASH)
				continue;
#endif
			// Pick up all the child directories of this directory and 'push' them on stack
			for (pDir = pDfEntry->dfe_pDirEntry->de_ChildDir;
				 pDir != NULL;
				 pDir = pDir->dfe_NextSibling)
			{
				Stack[StackPtr++] = pDir;
			}
		}

		AfpFreeMemory(Stack);
	}

	AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock);
}

#endif

#if	DBG

NTSTATUS FASTCALL
afpDumpDfeTree(
	IN	PVOID	Context
)
{
	PVOLDESC	pVolDesc;
	PDFENTRY	pDfEntry, pChild;
	LONG		i, StackPtr;

	if (afpDumpDfeTreeFlag)
	{
		afpDumpDfeTreeFlag = 0;

		for (pVolDesc = AfpVolumeList; pVolDesc != NULL; pVolDesc = pVolDesc->vds_Next)
		{
			StackPtr = 0;
			DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
					("Volume : %Z\n", &pVolDesc->vds_Name));
			afpDfeStack[StackPtr++] = pVolDesc->vds_pDfeRoot;

			while (StackPtr > 0)
			{
				pDfEntry = afpDfeStack[--StackPtr];
				afpDisplayDfe(pDfEntry);
				for (i = 0; i < MAX_CHILD_HASH_BUCKETS; i++)
				{
					for (pChild = pDfEntry->dfe_pDirEntry->de_ChildFile[i];
						 pChild != NULL;
						 pChild = pChild->dfe_NextSibling)
					{
						afpDisplayDfe(pChild);
					}
				}
				for (pChild = pDfEntry->dfe_pDirEntry->de_ChildDir;
					 pChild != NULL;
					 pChild = pChild->dfe_NextSibling)
				{
					afpDfeStack[StackPtr++] = pChild;
				}
			}
		}
	}

	return AFP_ERR_REQUEUE;
}


VOID FASTCALL
afpDisplayDfe(
	IN	PDFENTRY	pDfEntry
)
{
	USHORT	i;

	// Figure out the indenting. One space for every depth unit of parent
	// If this is a directory, a '+' and then the dir name
	// If this is a file, then just the file name

	for (i = 0; i < (pDfEntry->dfe_Parent->dfe_DirDepth + 1); i++)
	{
		DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
				("%c   ", 0xB3));
	}
	if (pDfEntry->dfe_NextSibling == NULL)
	{
		DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
				("%c%c%c%c", 0xC0, 0xC4, 0xC4, 0xC4));
	}
	else
	{
		DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
				("%c%c%c%c", 0xC3, 0xC4, 0xC4, 0xC4));
	}
	DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
			("%Z ", &pDfEntry->dfe_UnicodeName));

	if (pDfEntry->dfe_Flags & DFE_FLAGS_DIR)
	{
		DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
				("(%c, %lx, Id = %lx)\n", 0x9F, pDfEntry, pDfEntry->dfe_AfpId));
	}
	else
	{
		DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
				("(%c, %lx, Id = %lx)\n", 0x46, pDfEntry, pDfEntry->dfe_AfpId));
	}
}

#endif

