/*

Copyright (c) 1992  Microsoft Corporation

Module Name:

	fdparm.c

Abstract:

	This module contains the routines for handling file parameters.

Author:

	Jameel Hyder (microsoft!jameelh)


Revision History:
	25 Apr 1992		Initial Version

Notes:	Tab stop: 4
--*/

#define	_FDPARM_LOCALS
#define	FILENUM	FILE_FDPARM

#include <seposix.h>
#include <afp.h>
#include <fdparm.h>
#include <pathmap.h>
#include <afpinfo.h>
#include <client.h>
#include <access.h>

#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, AfpGetFileDirParmsReplyLength)
#pragma alloc_text( PAGE, AfpPackFileDirParms)
#pragma alloc_text( PAGE, AfpUnpackFileDirParms)
#pragma alloc_text( PAGE, AfpUnpackCatSearchSpecs)
#pragma alloc_text( PAGE, AfpSetFileDirParms)
#pragma alloc_text( PAGE, AfpQuerySecurityIdsAndRights)
#pragma alloc_text( PAGE, AfpConvertNTAttrToAfpAttr)
#pragma alloc_text( PAGE, AfpConvertAfpAttrToNTAttr)
#pragma alloc_text( PAGE, AfpNormalizeAfpAttr)
#pragma alloc_text( PAGE, AfpMapFDBitmapOpenAccess)
#pragma alloc_text( PAGE, AfpCheckForInhibit)
#pragma alloc_text( PAGE, AfpIsCatSearchMatch)
#endif


/***	AfpGetFileDirParmsReplyLength
 *
 *	Compute the size of buffer required to copy the file parameters based
 *	on the bitmap.
 */
USHORT
AfpGetFileDirParmsReplyLength(
	IN	PFILEDIRPARM	pFDParm,
	IN	DWORD			Bitmap
)
{
	LONG	i;
	USHORT	Size = 0;
	static	BYTE	Bitmap2Size[14] =
				{
					sizeof(USHORT),		// Attributes
					sizeof(DWORD),		// Parent DirId
					sizeof(DWORD),		// Create Date
					sizeof(DWORD),		// Mod. Date
					sizeof(DWORD),		// Backup Date
					sizeof(FINDERINFO),
					sizeof(USHORT) + sizeof(BYTE),	// Long Name
					sizeof(USHORT) + sizeof(BYTE),	// Short Name
					sizeof(DWORD),		// DirId/FileNum
					sizeof(DWORD),		// DataForkLength/Offspring Count
					sizeof(DWORD),		// RescForkLength/Owner Id
					sizeof(DWORD),		// Group Id
					sizeof(DWORD),		// Access Rights
					sizeof(PRODOSINFO)	// ProDos Info
				};

	PAGED_CODE( );

	ASSERT ((Bitmap & ~DIR_BITMAP_MASK) == 0);

	if (Bitmap & FD_BITMAP_LONGNAME)
		Size += pFDParm->_fdp_LongName.Length;

	if (Bitmap & FD_BITMAP_SHORTNAME)
		Size += pFDParm->_fdp_ShortName.Length;

	if (IsDir(pFDParm) && (Bitmap & DIR_BITMAP_OFFSPRINGS))
		Size -= sizeof(USHORT);

	for (i = 0; Bitmap; i++)
	{
		if (Bitmap & 1)
			Size += (USHORT)Bitmap2Size[i];
		Bitmap >>= 1;
	}
	return Size;
}



/***	AfpPackFileDirParms
 *
 *	Pack file or directory parameters into the reply buffer in on-the-wire
 *	format.
 */
VOID
AfpPackFileDirParms(
	IN	PFILEDIRPARM	pFDParm,
	IN	DWORD			Bitmap,
	IN	PBYTE			pReplyBuf
)
{
	LONG	Offset = 0;
	LONG	LongNameOff, ShortNameOff;

	PAGED_CODE( );

	if (Bitmap & FD_BITMAP_ATTR)
	{
		PUTSHORT2SHORT(pReplyBuf + Offset, pFDParm->_fdp_Attr);
		Offset += sizeof(USHORT);
	}
	if (Bitmap & FD_BITMAP_PARENT_DIRID)
	{
		PUTDWORD2DWORD(pReplyBuf + Offset, pFDParm->_fdp_ParentId);
		Offset += sizeof(DWORD);
	}
	if (Bitmap & FD_BITMAP_CREATETIME)
	{
		PUTDWORD2DWORD(pReplyBuf + Offset, pFDParm->_fdp_CreateTime);
		Offset += sizeof(DWORD);
	}
	if (Bitmap & FD_BITMAP_MODIFIEDTIME)
	{
		PUTDWORD2DWORD(pReplyBuf + Offset, pFDParm->_fdp_ModifiedTime);
		Offset += sizeof(DWORD);
	}
	if (Bitmap & FD_BITMAP_BACKUPTIME)
	{
		PUTDWORD2DWORD(pReplyBuf + Offset, pFDParm->_fdp_BackupTime);
		Offset += sizeof(DWORD);
	}
	if (Bitmap & FD_BITMAP_FINDERINFO)
	{
		if ((Bitmap & FD_BITMAP_ATTR) && (pFDParm->_fdp_Attr & FD_BITMAP_ATTR_INVISIBLE))
			pFDParm->_fdp_FinderInfo.fd_Attr1 |= FINDER_FLAG_INVISIBLE;

		RtlCopyMemory(pReplyBuf + Offset, (PBYTE)&pFDParm->_fdp_FinderInfo,
													sizeof(FINDERINFO));
		Offset += sizeof(FINDERINFO);
	}

	// Note the offset where the pointers to names will go. We'll have to
	// Fill it up later.
	if (Bitmap & FD_BITMAP_LONGNAME)
	{
		LongNameOff = Offset;
		Offset += sizeof(USHORT);
	}
	if (Bitmap & FD_BITMAP_SHORTNAME)
	{
		ShortNameOff = Offset;
		Offset += sizeof(USHORT);
	}

	// FileNum for files and DirId for Directories are in the same place
	if (Bitmap & FILE_BITMAP_FILENUM)
	{
		PUTDWORD2DWORD(pReplyBuf + Offset, pFDParm->_fdp_AfpId);
		Offset += sizeof(DWORD);
	}

	if (IsDir(pFDParm))
	{
		// Directory parameters
		if (Bitmap & DIR_BITMAP_OFFSPRINGS)
		{
			DWORD	OffSpring;
	
			OffSpring = pFDParm->_fdp_FileCount + pFDParm->_fdp_DirCount;
			PUTDWORD2SHORT(pReplyBuf + Offset, OffSpring);
			Offset += sizeof(USHORT);
		}

		if (Bitmap & DIR_BITMAP_OWNERID)
		{
			PUTDWORD2DWORD(pReplyBuf + Offset, pFDParm->_fdp_OwnerId);
			Offset += sizeof(DWORD);
		}
	
		if (Bitmap & DIR_BITMAP_GROUPID)
		{
			PUTDWORD2DWORD(pReplyBuf + Offset, pFDParm->_fdp_GroupId);
			Offset += sizeof(DWORD);
		}

		if (Bitmap & DIR_BITMAP_ACCESSRIGHTS)
		{
			DWORD	AccessInfo;
	
			AccessInfo = (pFDParm->_fdp_OwnerRights << OWNER_RIGHTS_SHIFT) +
						 (pFDParm->_fdp_GroupRights << GROUP_RIGHTS_SHIFT) +
						 (pFDParm->_fdp_WorldRights << WORLD_RIGHTS_SHIFT) +
						 (pFDParm->_fdp_UserRights  << USER_RIGHTS_SHIFT);
	
			PUTDWORD2DWORD(pReplyBuf + Offset, AccessInfo & ~OWNER_BITS_ALL);
			Offset += sizeof(DWORD);
		}
	}
	else
	{
		if (Bitmap & FILE_BITMAP_DATALEN)
		{
			PUTDWORD2DWORD(pReplyBuf + Offset, pFDParm->_fdp_DataForkLen);
			Offset += sizeof(DWORD);
		}

		// Resc Length for files and Owner Id for Directories are in the same place
		if (Bitmap & FILE_BITMAP_RESCLEN)
		{
			PUTDWORD2DWORD(pReplyBuf + Offset, pFDParm->_fdp_RescForkLen);
			Offset += sizeof(DWORD);
		}
	}

	if (Bitmap & FD_BITMAP_PRODOSINFO)
	{
		RtlCopyMemory(pReplyBuf + Offset, (PBYTE)&pFDParm->_fdp_ProDosInfo,
													sizeof(PRODOSINFO));
		Offset += sizeof(PRODOSINFO);
	}
	if (Bitmap & FD_BITMAP_LONGNAME)
	{
		ASSERT(pFDParm->_fdp_LongName.Length <= AFP_LONGNAME_LEN);

		PUTDWORD2SHORT(pReplyBuf + LongNameOff, Offset);
		PUTSHORT2BYTE(pReplyBuf + Offset, pFDParm->_fdp_LongName.Length);
		RtlCopyMemory(pReplyBuf + Offset + sizeof(BYTE),
					  pFDParm->_fdp_LongName.Buffer,
					  pFDParm->_fdp_LongName.Length);

		Offset += pFDParm->_fdp_LongName.Length + sizeof(BYTE);
	}
	if (Bitmap & FD_BITMAP_SHORTNAME)
	{
		ASSERT(pFDParm->_fdp_ShortName.Length <= AFP_SHORTNAME_LEN);

		PUTDWORD2SHORT(pReplyBuf + ShortNameOff, Offset);
		PUTSHORT2BYTE(pReplyBuf + Offset, pFDParm->_fdp_ShortName.Length);
		RtlCopyMemory(pReplyBuf + Offset + sizeof(BYTE),
					  pFDParm->_fdp_ShortName.Buffer,
					  pFDParm->_fdp_ShortName.Length);
		Offset += pFDParm->_fdp_ShortName.Length + sizeof(BYTE);
	}
	if (Offset & 1)
		*(pReplyBuf + Offset) = 0;
}



/***	AfpUnpackFileDirParms
 *
 *	Unpack the information from the on-the-wire format to the FileDirParm
 *	structure. Only the fields that can be set are looked at. The bitmaps
 *	are validated by the caller.
 *
 *	OPTIMIZATION:	The finder is notorious for setting things needlessly.
 *					We figure out if what is being set is same as what it
 *					is currently and if it is just clear that bit.
 */
AFPSTATUS
AfpUnpackFileDirParms(
	IN	PBYTE			pBuffer,
	IN	LONG			Length,
	IN	PDWORD			pBitmap,
	OUT	PFILEDIRPARM	pFDParm
)
{
	DWORD		Bitmap = *pBitmap;
	AFPTIME		ModTime;
	USHORT		Offset = 0;
	BOOLEAN		SetModTime = False;
	AFPSTATUS	Status = AFP_ERR_NONE;

	PAGED_CODE( );

	do
	{
		if ((LONG)AfpGetFileDirParmsReplyLength(pFDParm, *pBitmap) > Length)
		{
			DBGPRINT(DBG_COMP_AFPAPI_FD, DBG_LEVEL_FATAL,
				("UnpackFileDirParms: Buffer not large enough!\n"));
	        DBGBRK(DBG_LEVEL_FATAL);
			Status = AFP_ERR_PARAM;
			break;
		}

		if (Bitmap & FD_BITMAP_ATTR)
		{
			USHORT	OldAttr, NewAttr;
			USHORT	Set;

			GETSHORT2SHORT(&NewAttr, pBuffer+Offset);
			// keep track of if client wants to set bits or clear bits
			Set = (NewAttr & FD_BITMAP_ATTR_SET);
			// take off the 'set' bit to isolate the requested bits
			NewAttr &= ~FD_BITMAP_ATTR_SET;
			// the current effective settings of attributes
			OldAttr = (pFDParm->_fdp_Attr & ~FD_BITMAP_ATTR_SET);

			if ((NewAttr != 0) &&
				(((Set != 0) && ((OldAttr ^ NewAttr) != 0)) ||
				 ((Set == 0) && ((OldAttr & NewAttr) != 0))))
			{
				// becomes the new resultant AFP attributes after setting
				pFDParm->_fdp_EffectiveAttr = (Set != 0) ?
													(pFDParm->_fdp_Attr | NewAttr) :
													(pFDParm->_fdp_Attr & ~NewAttr);

				// changing a directory's inhibit and invisible attributes from
				// their current settings can only be done by the dir owner
				if (IsDir(pFDParm) &&
					((pFDParm->_fdp_EffectiveAttr & DIR_BITMAP_ATTR_CHG_X_OWNER_ONLY) ^
					 (pFDParm->_fdp_Attr & DIR_BITMAP_ATTR_CHG_X_OWNER_ONLY)) &&
					!(pFDParm->_fdp_UserRights & DIR_ACCESS_OWNER))
                {
					Status = AFP_ERR_ACCESS_DENIED;
					break;
				}

				// becomes attribute bits requested to be set/cleared
				pFDParm->_fdp_Attr = (NewAttr | Set);
			}
			else *pBitmap &= ~FD_BITMAP_ATTR;
			Offset += sizeof(USHORT);
		}
		if (Bitmap & FD_BITMAP_CREATETIME)
		{
			AFPTIME	CreateTime;

			GETDWORD2DWORD(&CreateTime, pBuffer+Offset);
			if (CreateTime == pFDParm->_fdp_CreateTime)
				*pBitmap &= ~FD_BITMAP_CREATETIME;
			else pFDParm->_fdp_CreateTime = CreateTime;
			Offset += sizeof(DWORD);
		}
		if (Bitmap & FD_BITMAP_MODIFIEDTIME)
		{
			GETDWORD2DWORD(&ModTime, pBuffer+Offset);
			if (ModTime == pFDParm->_fdp_ModifiedTime)
			{
				*pBitmap &= ~FD_BITMAP_MODIFIEDTIME;
				SetModTime = True;
			}
			else
			{
				pFDParm->_fdp_ModifiedTime = ModTime;
			}
			Offset += sizeof(DWORD);
		}
		if (Bitmap & FD_BITMAP_BACKUPTIME)
		{
			AFPTIME	BackupTime;

			GETDWORD2DWORD(&BackupTime, pBuffer+Offset);
			if (BackupTime == pFDParm->_fdp_BackupTime)
				*pBitmap &= ~FD_BITMAP_BACKUPTIME;
			else pFDParm->_fdp_BackupTime = BackupTime;
			Offset += sizeof(DWORD);
		}
		if (Bitmap & FD_BITMAP_FINDERINFO)
		{
			int		i, rlo = -1, rhi = -1;	// Range of bytes that are different
			PBYTE	pSrc, pDst;

			pSrc = pBuffer + Offset;
			pDst = (PBYTE)(&pFDParm->_fdp_FinderInfo);
			for (i = 0; i < sizeof(FINDERINFO); i++)
			{
				if (*pSrc++ != *pDst++)
				{
					if (rlo == -1)
						rlo = i;
					else rhi = i;
				}
			}

			if ((rlo != -1) && (rhi == -1))
				rhi = rlo;

			// Optimization: if nothing has changed, avoid a copy
			if (rlo == -1)
			{
				*pBitmap &= ~FD_BITMAP_FINDERINFO;
			}
			else
			{
				RtlCopyMemory((PBYTE)&pFDParm->_fdp_FinderInfo,
							  pBuffer+Offset,
							  sizeof(FINDERINFO));
			}
			Offset += sizeof(FINDERINFO);
		}

		if (IsDir(pFDParm) &&
			(Bitmap & (DIR_BITMAP_OWNERID |
					   DIR_BITMAP_GROUPID |
					   DIR_BITMAP_ACCESSRIGHTS)))
		{
			if (Bitmap & DIR_BITMAP_OWNERID)
			{
				DWORD	OwnerId;

				GETDWORD2DWORD(&OwnerId, pBuffer+Offset);
				if (pFDParm->_fdp_OwnerId == OwnerId)
					Bitmap &= ~DIR_BITMAP_OWNERID;
				else pFDParm->_fdp_OwnerId = OwnerId;
				Offset += sizeof(DWORD);
			}
			if (Bitmap & DIR_BITMAP_GROUPID)
			{
				DWORD	GroupId;

				GETDWORD2DWORD(&GroupId, pBuffer+Offset);
				if (pFDParm->_fdp_GroupId == GroupId)
					Bitmap &= ~DIR_BITMAP_GROUPID;
				else pFDParm->_fdp_GroupId = GroupId;
				Offset += sizeof(DWORD);
			}
			if (Bitmap & DIR_BITMAP_ACCESSRIGHTS)
			{
				DWORD	AccessInfo;

				GETDWORD2DWORD(&AccessInfo, pBuffer+Offset);

				pFDParm->_fdp_OwnerRights  =
						(BYTE)((AccessInfo >> OWNER_RIGHTS_SHIFT) & DIR_ACCESS_ALL);
				pFDParm->_fdp_GroupRights =
						(BYTE)((AccessInfo >> GROUP_RIGHTS_SHIFT) & DIR_ACCESS_ALL);
				pFDParm->_fdp_WorldRights =
						(BYTE)((AccessInfo >> WORLD_RIGHTS_SHIFT) & DIR_ACCESS_ALL);
				Offset += sizeof(DWORD);
			}
			if (Bitmap & (DIR_BITMAP_OWNERID | DIR_BITMAP_GROUPID))
				Bitmap |= DIR_BITMAP_ACCESSRIGHTS;
		}

		if (Bitmap & FD_BITMAP_PRODOSINFO)
		{
			int		i;
			PBYTE	pSrc, pDst;

			pSrc = pBuffer + Offset;
			pDst = (PBYTE)(&pFDParm->_fdp_ProDosInfo);
			for (i = 0; i < sizeof(PRODOSINFO); i++)
				if (*pSrc++ != *pDst++)
					break;
			if (i == sizeof(PRODOSINFO))
				*pBitmap &= ~FD_BITMAP_PRODOSINFO;
			else RtlCopyMemory((PBYTE)&pFDParm->_fdp_ProDosInfo,
								pBuffer+Offset,
								sizeof(PRODOSINFO));
			// Offset += sizeof(PRODOSINFO);
		}
	} while (False);

	// If anything is being set and modified time was dropped because it was identical
	// to what is already on, set the bitmap so that it is restored after the change.
	if (SetModTime && *pBitmap)
	{
		*pBitmap |= FD_BITMAP_MODIFIEDTIME;
	}

	return Status;
}


/***	AfpSetFileDirParms
 *
 *	This is the worker for AfpGetFileDirParms/AfpSetFileParms/AfpSetDirParms.
 *	This is callable only in the worker's context.
 *
 *  LOCKS: vds_IdDbAccessLock (SWMR, Exclusive);
 */
AFPSTATUS
AfpSetFileDirParms(
	IN  PVOLDESC		pVolDesc,
	IN  PPATHMAPENTITY	pPME,
	IN  DWORD			Bitmap,
	IN	PFILEDIRPARM	pFDParm
)
{
	AFPSTATUS		Status = AFP_ERR_NONE;
	BOOLEAN			CleanupLock = False, SetROAttr = False;
	PDFENTRY		pDfEntry = NULL;

	PAGED_CODE( );

	do
	{
        ASSERT(IS_VOLUME_NTFS(pVolDesc));

		// NOTE: should we take the SWMR while we set permissions?
		if (IsDir(pFDParm))
		{
			if (Bitmap & (DIR_BITMAP_OWNERID |
						  DIR_BITMAP_GROUPID |
						  DIR_BITMAP_ACCESSRIGHTS))
			{

				Status = AfpSetAfpPermissions(pPME->pme_Handle.fsh_FileHandle,
											  Bitmap,
											  pFDParm);
				if (!NT_SUCCESS(Status))
					break;
			}
		}

		if (Bitmap & (FD_BITMAP_FINDERINFO |
					  FD_BITMAP_PRODOSINFO |
					  FD_BITMAP_ATTR |
					  FD_BITMAP_BACKUPTIME |
					  DIR_BITMAP_ACCESSRIGHTS |
					  DIR_BITMAP_OWNERID   |
					  DIR_BITMAP_GROUPID   |
					  FD_BITMAP_CREATETIME |
					  FD_BITMAP_MODIFIEDTIME |
					  FD_BITMAP_ATTR))
		{
			AfpSwmrAcquireExclusive(&pVolDesc->vds_IdDbAccessLock);
			CleanupLock = True;
		}

		if (Bitmap & (FD_BITMAP_FINDERINFO |
					  FD_BITMAP_PRODOSINFO |
					  FD_BITMAP_ATTR |
					  FD_BITMAP_BACKUPTIME |
					  DIR_BITMAP_ACCESSRIGHTS))
		{

			// Will update the cached AfpInfo as well as the stream
			Status = AfpSetAfpInfo(&pPME->pme_Handle,
								   Bitmap,
								   pFDParm,
								   pVolDesc,
								   &pDfEntry);

			if (Status != AFP_ERR_NONE)
				break;
		}

		if (Bitmap & (FD_BITMAP_CREATETIME |
					  FD_BITMAP_MODIFIEDTIME |
					  FD_BITMAP_ATTR |
					  DIR_BITMAP_ACCESSRIGHTS |
					  DIR_BITMAP_OWNERID |
					  DIR_BITMAP_GROUPID))
		{
			DWORD	SetNtAttr = 0, ClrNtAttr = 0;

			// need to update the cached times too.  If we didn't get the
			// pDfEntry back from setting some other AfpInfo, look it up now
			if (pDfEntry == NULL)
			{
				pDfEntry = AfpFindDfEntryById(pVolDesc,
											   pFDParm->_fdp_AfpId,
											   IsDir(pFDParm) ? DFE_DIR : DFE_FILE);
				if (pDfEntry == NULL)
				{
					Status = AFP_ERR_OBJECT_NOT_FOUND;
					break;
				}
			}

			if (Bitmap & FD_BITMAP_ATTR)
			{
				if (pFDParm->_fdp_Attr & FD_BITMAP_ATTR_SET)
					SetNtAttr = AfpConvertAfpAttrToNTAttr(pFDParm->_fdp_Attr);
				else
					ClrNtAttr = AfpConvertAfpAttrToNTAttr(pFDParm->_fdp_Attr);

                if (pFDParm->_fdp_Attr & (FD_BITMAP_ATTR_RENAMEINH |
										  FD_BITMAP_ATTR_DELETEINH))
				{
					SetROAttr = True;
				}

				if ((SetNtAttr == 0) && (ClrNtAttr == 0))
				{
					// Since there is no attribute being set/cleared that
					// corresponds to any NT attribute, we can clear the
					// ATTR bitmap since we've already set the Mac specific
					// stuff in the DFE and Afpinfo.
					Bitmap &= ~FD_BITMAP_ATTR;
				}
			}

			if (Bitmap & (FD_BITMAP_CREATETIME |
						  FD_BITMAP_MODIFIEDTIME |
						  FD_BITMAP_ATTR))
			{
				ASSERT(pPME->pme_FullPath.Buffer != NULL);
				Status = AfpIoSetTimesnAttr(&pPME->pme_Handle,
						((Bitmap & FD_BITMAP_CREATETIME) != 0) ?
									(PAFPTIME)&pFDParm->_fdp_CreateTime : NULL,
						(((Bitmap & FD_BITMAP_MODIFIEDTIME) != 0) || (SetROAttr)) ?
									(PAFPTIME)&pFDParm->_fdp_ModifiedTime : NULL,
						SetNtAttr, ClrNtAttr,
						pVolDesc,
						&pPME->pme_FullPath);
			}


			if (!NT_SUCCESS(Status))
				break;

			if (Bitmap & FD_BITMAP_CREATETIME)
			{
				pDfEntry->dfe_CreateTime =
								(AFPTIME)pFDParm->_fdp_CreateTime;
			}

			if (Bitmap & FD_BITMAP_MODIFIEDTIME)
			{
				AfpConvertTimeFromMacFormat(pFDParm->_fdp_ModifiedTime,
											&pDfEntry->dfe_LastModTime);
			}
			else if (IsDir(pFDParm) &&
					 ((Bitmap & (DIR_BITMAP_OWNERID |
						 	     DIR_BITMAP_GROUPID |
							     DIR_BITMAP_ACCESSRIGHTS)) ||
					   SetROAttr))
			{
				ASSERT(VALID_DFE(pDfEntry));
				// Setting permissions on a dir or changing its RO attribute
				// should update the modified time on the dir (as observed
				// on Appleshare 4.0)
				AfpIoChangeNTModTime(&pPME->pme_Handle,
									 &pDfEntry->dfe_LastModTime);
			}

			if (Bitmap & FD_BITMAP_ATTR)
			{
				if (pFDParm->_fdp_Attr & FD_BITMAP_ATTR_SET)
					 pDfEntry->dfe_NtAttr |= (USHORT)SetNtAttr;
				else pDfEntry->dfe_NtAttr &= ~((USHORT)ClrNtAttr);

			}

		}

		AfpVolumeSetModifiedTime(pVolDesc);
	} while (False);

	if (CleanupLock)
	{
		AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock);
	}

	return Status;
}


/***	AfpQuerySecurityIdsAndRights
 *
 *	Find the owner id and primary group id for this entity. Map the corres.
 *	SIDs to their posix ids. Determine also the access rights for owner,
 *	group, world and this user. The access rights are divided into the
 *	following:
 *
 *	Owner's rights
 *	Primary group's rights
 *	World rights
 *	This user's rights
 *
 *	The handle to the directory should be opened with READ_CONTROL
 *	See Files vs. See Folders resolution for Owner/Group/World is already done.
 */
LOCAL AFPSTATUS
AfpQuerySecurityIdsAndRights(
	IN	PSDA			pSda,
	IN	PFILESYSHANDLE	pFSHandle,
	IN	DWORD			Bitmap,
	IN OUT PFILEDIRPARM	pFDParm
)
{
	NTSTATUS	Status;
	BYTE		ORights, GRights, WRights;

	PAGED_CODE( );

	// Save contents of the AfpInfo stream access bits
	ORights = pFDParm->_fdp_OwnerRights | DIR_ACCESS_WRITE;
	GRights = pFDParm->_fdp_GroupRights | DIR_ACCESS_WRITE;
	WRights = pFDParm->_fdp_WorldRights | DIR_ACCESS_WRITE;

	// Initialize to no rights for everybody
	pFDParm->_fdp_Rights = 0;

	// Get the OwnerId and GroupId for this directory.
	// Determine the Owner/Group and World rights for this directory
	// Determine if this user is a member of the directory's group
	Status = AfpGetAfpPermissions(pSda,
								  pFSHandle->fsh_FileHandle,
								  pFDParm);
	if (!NT_SUCCESS(Status))
		return Status;

	// Modify owner/group/world rights for the SeeFiles/SeeFolder weirdness
	// Also if the ACLs say we have READ & SEARCH access but AfpInfo stream
	// says we don't, then ignore AfpInfo stream
	if ((pFDParm->_fdp_OwnerRights & (DIR_ACCESS_READ | DIR_ACCESS_SEARCH)) &&
		!(ORights & (DIR_ACCESS_READ | DIR_ACCESS_SEARCH)))
		ORights |= (DIR_ACCESS_READ | DIR_ACCESS_SEARCH);
	pFDParm->_fdp_OwnerRights &= ORights;

	if ((pFDParm->_fdp_GroupRights & (DIR_ACCESS_READ | DIR_ACCESS_SEARCH)) &&
		!(GRights & (DIR_ACCESS_READ | DIR_ACCESS_SEARCH)))
		GRights |= (DIR_ACCESS_READ | DIR_ACCESS_SEARCH);
	pFDParm->_fdp_GroupRights &= GRights;

	if ((pFDParm->_fdp_WorldRights & (DIR_ACCESS_READ | DIR_ACCESS_SEARCH)) &&
		!(WRights & (DIR_ACCESS_READ | DIR_ACCESS_SEARCH)))
		WRights |= (DIR_ACCESS_READ | DIR_ACCESS_SEARCH);
	pFDParm->_fdp_WorldRights &= WRights;

	// One last bit of munging. Owner & Group can be the same and they both
	// could be everyone !! Coalese that.
	if (pFDParm->_fdp_OwnerId == SE_WORLD_POSIX_ID)
	{
		pFDParm->_fdp_WorldRights |= (pFDParm->_fdp_OwnerRights & ~DIR_ACCESS_OWNER);
		pFDParm->_fdp_OwnerRights |= pFDParm->_fdp_WorldRights;
	}

	if (pFDParm->_fdp_GroupId == SE_WORLD_POSIX_ID)
	{
		pFDParm->_fdp_WorldRights |= pFDParm->_fdp_GroupRights;
		pFDParm->_fdp_GroupRights |= pFDParm->_fdp_WorldRights;
	}

	if (pFDParm->_fdp_GroupId == pFDParm->_fdp_OwnerId)
	{
		pFDParm->_fdp_OwnerRights |= pFDParm->_fdp_GroupRights;
		pFDParm->_fdp_GroupRights |= (pFDParm->_fdp_OwnerRights & ~DIR_ACCESS_OWNER);
	}

	// Modify User rights for the SeeFiles/SeeFolder weirdness by determining
	// if the user is owner/group or world
	if (pFDParm->_fdp_UserRights & (DIR_ACCESS_READ | DIR_ACCESS_SEARCH))
	{
		BYTE	URights = (pFDParm->_fdp_UserRights & (DIR_ACCESS_WRITE | DIR_ACCESS_OWNER));

		if ((pFDParm->_fdp_WorldRights & (DIR_ACCESS_READ | DIR_ACCESS_SEARCH))
									!= (DIR_ACCESS_READ | DIR_ACCESS_SEARCH))
		{
			pFDParm->_fdp_UserRights = pFDParm->_fdp_WorldRights;
			if (pFDParm->_fdp_UserIsOwner)
			{
				pFDParm->_fdp_UserRights |= pFDParm->_fdp_OwnerRights;
			}
			if (pFDParm->_fdp_UserIsMemberOfDirGroup)
			{
				pFDParm->_fdp_UserRights |= pFDParm->_fdp_GroupRights;
			}
			if ((pFDParm->_fdp_UserRights & (DIR_ACCESS_READ | DIR_ACCESS_SEARCH)) == 0)
				pFDParm->_fdp_UserRights |= (DIR_ACCESS_READ | DIR_ACCESS_SEARCH);
			pFDParm->_fdp_UserRights &= ~DIR_ACCESS_WRITE;
			pFDParm->_fdp_UserRights |= URights;
		}
	}

	return Status;
}


/***	AfpConvertNTAttrToAfpAttr
 *
 *	Map NT Attributes to the AFP equivalents.
 */
USHORT
AfpConvertNTAttrToAfpAttr(
	IN	DWORD	Attr
)
{
	USHORT	AfpAttr = FD_BITMAP_ATTR_SET;

	PAGED_CODE( );

	if (Attr & FILE_ATTRIBUTE_READONLY)
	{
		AfpAttr |= FD_BITMAP_ATTR_RENAMEINH | FD_BITMAP_ATTR_DELETEINH;
		if (!(Attr & FILE_ATTRIBUTE_DIRECTORY))
			AfpAttr |= FILE_BITMAP_ATTR_WRITEINH;
	}

	if (Attr & FILE_ATTRIBUTE_HIDDEN)
		AfpAttr |= FD_BITMAP_ATTR_INVISIBLE;

	if (Attr & FILE_ATTRIBUTE_SYSTEM)
		AfpAttr |= FD_BITMAP_ATTR_SYSTEM;

	if (Attr & FILE_ATTRIBUTE_ARCHIVE)
	{
		AfpAttr |= FD_BITMAP_ATTR_BACKUPNEED;
	}

	return AfpAttr;
}


/***	AfpConvertAfpAttrToNTAttr
 *
 *	Map AFP Attributes to the NT equivalents.
 */
DWORD
AfpConvertAfpAttrToNTAttr(
	IN	USHORT	Attr
)
{
	DWORD	NtAttr = 0;

	PAGED_CODE( );

	if (Attr & (FD_BITMAP_ATTR_RENAMEINH |
				FD_BITMAP_ATTR_DELETEINH |
				FILE_BITMAP_ATTR_WRITEINH))
		NtAttr |= FILE_ATTRIBUTE_READONLY;

	if (Attr & FD_BITMAP_ATTR_INVISIBLE)
		NtAttr |= FILE_ATTRIBUTE_HIDDEN;

	if (Attr & FD_BITMAP_ATTR_SYSTEM)
		NtAttr |= FILE_ATTRIBUTE_SYSTEM;

	if (Attr & FD_BITMAP_ATTR_BACKUPNEED)
	{
		NtAttr |= FILE_ATTRIBUTE_ARCHIVE;
	}

	return NtAttr;
}


/***	AfpNormalizeAfpAttr
 *
 *	Normalize the various inhibit bits in afp attributes vs. the RO bit on the
 *	disk.
 */
VOID
AfpNormalizeAfpAttr(
	IN OUT	PFILEDIRPARM	pFDParm,
	IN		DWORD			NtAttr
)
{
	USHORT	AfpAttr;

	PAGED_CODE( );

	AfpAttr = AfpConvertNTAttrToAfpAttr(NtAttr);

	/*
	 *	The Attributes fall into two classes, the ones that are on
	 *	on the filesystem and the others maintained in the AfpInfo
	 *	stream. We need to coalesce these two sets. The RO bit on
	 *	the disk corres. to the three inhibit bits. Fine grain
	 *	control is possible.
	 *
	 *	The other set of bits that are in the exclusive realm of
	 *	the AfpInfo stream are the RAlreadyOpen and DAlreadyOpen
	 *	bits and the multi-user bit.
	 */
	if (((pFDParm->_fdp_Attr & FD_BITMAP_ATTR_NT_RO) == 0) ^
		((AfpAttr & FD_BITMAP_ATTR_NT_RO) == 0))
	{
		if ((AfpAttr & FD_BITMAP_ATTR_NT_RO) == 0)
			 pFDParm->_fdp_Attr &= ~FD_BITMAP_ATTR_NT_RO;
		else pFDParm->_fdp_Attr |= FD_BITMAP_ATTR_NT_RO;
	}

	pFDParm->_fdp_Attr &= (AfpAttr |
							(FILE_BITMAP_ATTR_MULTIUSER |
							 FILE_BITMAP_ATTR_DATAOPEN  |
							 FILE_BITMAP_ATTR_RESCOPEN  |
							 FD_BITMAP_ATTR_SET));
	pFDParm->_fdp_Attr |= (AfpAttr & (FD_BITMAP_ATTR_BACKUPNEED |
									  FD_BITMAP_ATTR_SYSTEM |
									  FD_BITMAP_ATTR_INVISIBLE));

}


/***AfpMapFDBitmapOpenAccess
 *
 *	Map the FD_INTERNAL_BITMAP_OPENACCESS_xxx bits to the appropriate
 *  FILEIO_ACCESS_xxx values.  The returned OpenAccess is used by the
 *  pathmap code to open the data stream of a file/dir (under impersonation
 *  for NTFS) for use in the AFP APIs.
 *
 */
DWORD
AfpMapFDBitmapOpenAccess(
	IN	DWORD	Bitmap,
	IN	BOOLEAN IsDir
)
{
	DWORD	OpenAccess = FILEIO_ACCESS_NONE;

	PAGED_CODE( );

	do
	{
		if (!(Bitmap & FD_INTERNAL_BITMAP_OPENACCESS_ALL))
		{
			break;
		}
		if (Bitmap & FD_INTERNAL_BITMAP_OPENACCESS_READCTRL)
		{
			// For GetFileDirParms we don't know if it was a file or dir they
			// are asking for, so we had to OR the file and dir bitmaps together
			// before pathmapping.
			if (IsDir)
				OpenAccess = (FILEIO_ACCESS_NONE |
							  READ_CONTROL |
							  SYNCHRONIZE);
			break;
		}
		// Used by AfpAdmwDirectoryGetInfo
		if (Bitmap & FD_INTERNAL_BITMAP_OPENACCESS_ADMINGET)
		{
			OpenAccess = (FILE_READ_ATTRIBUTES |
						  READ_CONTROL |
						  SYNCHRONIZE);
			break;
		}
		if (Bitmap & FD_INTERNAL_BITMAP_OPENACCESS_ADMINSET)
		{
			OpenAccess = (FILE_READ_ATTRIBUTES |
						  READ_CONTROL |
						  SYNCHRONIZE |
						  FILE_WRITE_ATTRIBUTES |
						  WRITE_DAC |
						  WRITE_OWNER);
			break;
		}
		if (Bitmap & FD_INTERNAL_BITMAP_OPENACCESS_RW_ATTR)
		{
			OpenAccess |= (FILEIO_ACCESS_NONE | FILE_WRITE_ATTRIBUTES);
		}
		if (Bitmap & FD_INTERNAL_BITMAP_OPENACCESS_RWCTRL)
		{
			OpenAccess |= (FILEIO_ACCESS_NONE |
						   READ_CONTROL |
						   WRITE_DAC |
						   WRITE_OWNER |
						   SYNCHRONIZE);
			break;
		}
		if (Bitmap & FD_INTERNAL_BITMAP_OPENACCESS_READ)
		{
			OpenAccess |= FILEIO_ACCESS_READ;
		}
		if (Bitmap & FD_INTERNAL_BITMAP_OPENACCESS_WRITE)
		{
			OpenAccess |= FILEIO_ACCESS_WRITE;
		}
		if (Bitmap & FD_INTERNAL_BITMAP_OPENACCESS_DELETE)
		{
			OpenAccess |= FILEIO_ACCESS_DELETE;
		}
	} while (False);

	return OpenAccess;
}


/*** AfpCheckForInhibit
 *
 *  This routine checks for the setting of the Afp RenameInhibit, DeleteInhibit
 *  or WriteInhibit attributes.  It first queries for the host File/Dir
 *  attributes to find out the setting of the ReadOnly attribute, then checks
 *  that against the Afp InhibitBit of interest.  AFP_ERR_NONE is returned if
 *  the InhibitBit is not set, else AFP_ERR_OBJECT_LOCKED is returned.
 *  The input handle must be a handle to the $DATA stream of the file/dir open
 *  in server's context.  The host attributes are returned in pNTAttr if the
 *  error code is not AFP_ERR_MISC.
 *
 */
AFPSTATUS
AfpCheckForInhibit(
	IN	PFILESYSHANDLE	hData,		// handle to DATA stream in server context
	IN	DWORD			InhibitBit,
	IN	DWORD			AfpAttr,
	OUT PDWORD			pNTAttr
)
{
	AFPSTATUS	Status = STATUS_SUCCESS;

	PAGED_CODE();

	do
	{
		if (!NT_SUCCESS(AfpIoQueryTimesnAttr(hData, NULL, NULL, pNTAttr)))
		{
			Status = AFP_ERR_MISC;
			break;
		}

		if (!(*pNTAttr & FILE_ATTRIBUTE_READONLY))
		{
	        Status = AFP_ERR_NONE;
			break;
		}
		if (!(AfpAttr & FD_BITMAP_ATTR_NT_RO) || (AfpAttr & InhibitBit))
		{
			// The file/dir is ReadOnly, but NONE of the AFP Inhibit bits are
			// set, so we assume the PC has set the RO bit; or, the requested
			// inhibit bit IS set.
			Status = AFP_ERR_OBJECT_LOCKED;
			break;
		}
	} while (False);

	return Status;
}

/***	AfpUnpackCatSearchSpecs
 *
 *	Unpack the information from the on-the-wire format to the FileDirParm
 *	structures for Specification 1 and 2.  Specification 1 contains the
 *  CatSearch criteria for lower bounds and values.  Specification 2
 *  contains the CatSearch criteria for upper bounds and masks.  The parameters
 *  are packed in the same order that the bits are set in the request bitmap.
 *  These are read into FILEDIRPARM structures.
 *
 *	The fields in Specification 1 and Specification 2 have different uses:
 *
 *	- In the name field, Specification 1 holds the target string and
 *    Specification 2 must always have a nil name field.
 *
 *	- In all date and length fields, Specification 1 holds the lowest value
 *    in the target range and Specification 2 holds the highest value in the
 *    target range.
 *
 *	- In file attributes and Finder Info fields, Specification 1 holds the
 *    target value, and Specification 2 holds the bitwise mask that specifies
 *    which bits in that field in Specification 1 are relevant to the current
 *    search.
 *
 */
AFPSTATUS
AfpUnpackCatSearchSpecs(
	IN	PBYTE			pBuffer,		// Pointer to beginning of Spec data
	IN	USHORT			BufLength,		// Length of Spec1 + Spec2 data
	IN	DWORD			Bitmap,
	OUT	PFILEDIRPARM	pFDParm1,
	OUT PFILEDIRPARM	pFDParm2,
	OUT PUNICODE_STRING	pMatchString
)
{
	PCATSEARCHSPEC	pSpec1, pSpec2;
	PBYTE			pBuffer1, pBuffer2, pEndOfBuffer;
	USHORT			Offset = 0, MinSpecLength1, MinSpecLength2;
	AFPSTATUS		Status = AFP_ERR_NONE;
	BOOLEAN			NoNullString = False;

	PAGED_CODE( );

	pSpec1 = (PCATSEARCHSPEC) pBuffer;
	pSpec2 = (PCATSEARCHSPEC) ((PBYTE)pBuffer + sizeof(CATSEARCHSPEC) +
			  pSpec1->__StructLength);

	// Point to data after the specification length and filler byte

	pBuffer1 = (PBYTE)pSpec1 + sizeof(CATSEARCHSPEC);
	pBuffer2 = (PBYTE)pSpec2 + sizeof(CATSEARCHSPEC);

	do
	{
		//
        // Make sure pSpec2 is at least pointing into the buffer we have, and
		// that its length is within the buffer as well.
		//
		pEndOfBuffer = pBuffer + BufLength;

		if (((PBYTE)pSpec2 >= pEndOfBuffer) ||
			((PBYTE)pSpec2+pSpec2->__StructLength+sizeof(CATSEARCHSPEC)
			  > pEndOfBuffer))
		{
			DBGPRINT(DBG_COMP_AFPAPI_FD, DBG_LEVEL_ERR,
				("UnpackCatSearchParms: Buffer not large enough!\n"));
			Status = AFP_ERR_PARAM;
			break;
		}

		//
		// Validate that input buffer is long enough to hold all the info the
		// bitmap says it does.  Note we cannot yet account for the length of
		// a longname string's characters if there was one specified.
		//
		MinSpecLength1 = MinSpecLength2 = sizeof(CATSEARCHSPEC) +
						AfpGetFileDirParmsReplyLength(pFDParm1, Bitmap);

        //
		// HACK: In order to support LLPT, if the catsearch is
		// asking to match filename, we should allow the Spec2 name to
		// be missing from the buffer (as opposed to being the null string),
		// but still expect the offset to the name.
		//
		// We also need to support system 7.1 who sends a zero length spec2
		// if the Bitmap == FD_BITMAP_LONGNAME.
		//
		// Real Appleshare handles both these cases with no error.
		//

		if (Bitmap & FD_BITMAP_LONGNAME)
		{
			if (pSpec2->__StructLength == (MinSpecLength2-sizeof(CATSEARCHSPEC)-sizeof(BYTE)) )
			{
				MinSpecLength2 -= sizeof(BYTE);
				NoNullString = True;
			}
			else if ((Bitmap == FD_BITMAP_LONGNAME) && (pSpec2->__StructLength == 0))
			{
				MinSpecLength2 -= sizeof(USHORT) + sizeof(BYTE);
				NoNullString = True;
			}
		}

		if ( ((MinSpecLength1 + MinSpecLength2) > BufLength) ||
			 (pSpec1->__StructLength < (MinSpecLength1-sizeof(CATSEARCHSPEC))) ||
			 (pSpec2->__StructLength < (MinSpecLength2-sizeof(CATSEARCHSPEC))) )
		{
			DBGPRINT(DBG_COMP_AFPAPI_FD, DBG_LEVEL_ERR,
				("UnpackCatSearchParms: Buffer not large enough!\n"));
			Status = AFP_ERR_PARAM;
			break;
		}

		if (Bitmap & FD_BITMAP_ATTR)
		{

			GETSHORT2SHORT(&pFDParm1->_fdp_Attr, pBuffer1+Offset);
			GETSHORT2SHORT(&pFDParm2->_fdp_Attr, pBuffer2+Offset);
			if ((pFDParm2->_fdp_Attr & ~FD_BITMAP_ATTR_NT_RO) ||
				(pFDParm2->_fdp_Attr == 0))
			{
				Status = AFP_ERR_PARAM;
				break;
			}
			Offset += sizeof(USHORT);
		}
		if (Bitmap & FD_BITMAP_PARENT_DIRID)
		{
			GETDWORD2DWORD(&pFDParm1->_fdp_ParentId, pBuffer1+Offset);
			GETDWORD2DWORD(&pFDParm2->_fdp_ParentId, pBuffer2+Offset);
			if (pFDParm1->_fdp_ParentId < AFP_ID_ROOT)
			{
				Status = AFP_ERR_PARAM;
				break;
			}
			Offset += sizeof(DWORD);
		}
		if (Bitmap & FD_BITMAP_CREATETIME)
		{
			GETDWORD2DWORD(&pFDParm1->_fdp_CreateTime, pBuffer1+Offset);
			GETDWORD2DWORD(&pFDParm2->_fdp_CreateTime, pBuffer2+Offset);
			Offset += sizeof(DWORD);
		}
		if (Bitmap & FD_BITMAP_MODIFIEDTIME)
		{
			GETDWORD2DWORD(&pFDParm1->_fdp_ModifiedTime, pBuffer1+Offset);
			GETDWORD2DWORD(&pFDParm2->_fdp_ModifiedTime, pBuffer2+Offset);
			Offset += sizeof(DWORD);
		}
		if (Bitmap & FD_BITMAP_BACKUPTIME)
		{
			GETDWORD2DWORD(&pFDParm1->_fdp_BackupTime, pBuffer1+Offset);
			GETDWORD2DWORD(&pFDParm2->_fdp_BackupTime, pBuffer2+Offset);
			Offset += sizeof(DWORD);
		}
		if (Bitmap & FD_BITMAP_FINDERINFO)
		{
			RtlCopyMemory((PBYTE)&pFDParm1->_fdp_FinderInfo,
						  pBuffer1+Offset,
						  sizeof(FINDERINFO));

			RtlCopyMemory((PBYTE)&pFDParm2->_fdp_FinderInfo,
						  pBuffer2+Offset,
						  sizeof(FINDERINFO));

			Offset += sizeof(FINDERINFO);
		}
		if (Bitmap & FD_BITMAP_LONGNAME)
		{
			DWORD	NameOffset1, NameOffset2;

			//
			// Get the parm relative offset to the start of the pascal string
			//

			GETSHORT2DWORD(&NameOffset1, pBuffer1+Offset);
			if ((Bitmap == FD_BITMAP_LONGNAME) && (pSpec2->__StructLength == 0))
			{
				// HACK for system 7.1
				NameOffset2 = NameOffset1;
				pBuffer2 = NULL;
			}
			else
				GETSHORT2DWORD(&NameOffset2, pBuffer2+Offset);

			if ((NameOffset1 != NameOffset2) ||
				(pBuffer1 + NameOffset1 >= (PBYTE)pSpec2) ||
				(pBuffer2 + NameOffset2 > pEndOfBuffer))
			{
				Status = AFP_ERR_PARAM;
				break;
			}
			Offset += sizeof(USHORT);

			//
			// Get the pascal string length
			//

			GETBYTE2SHORT(&pFDParm1->_fdp_LongName.Length, pBuffer1+NameOffset1);

			// HACK: In order to support LLPT and system 7.1, if the catsearch is
			// asking to match filename, we should allow the Spec2 name to
			// be missing from the buffer (as opposed to being the null string).
			// Real Appleshare handles this case with no error.
			if (NoNullString)
				pFDParm2->_fdp_LongName.Length = 0;
			else
				GETBYTE2SHORT(&pFDParm2->_fdp_LongName.Length, pBuffer2+NameOffset1);

			if ((pFDParm1->_fdp_LongName.Length > AFP_LONGNAME_LEN) ||
				(pFDParm2->_fdp_LongName.Length != 0) ||
				(pBuffer1+NameOffset1+sizeof(BYTE)+pFDParm1->_fdp_LongName.Length > (PBYTE)pSpec2))
			{
				// Specification 2 must always have a nil name field.  Also
				// ensure that Specification 1 does not have a bogus string
				// length.
				Status = AFP_ERR_PARAM;
				break;
			}
			RtlCopyMemory(pFDParm1->_fdp_LongName.Buffer,
						  pBuffer1+NameOffset1+sizeof(BYTE),
						  pFDParm1->_fdp_LongName.Length);
            AfpConvertStringToMungedUnicode(&pFDParm1->_fdp_LongName, pMatchString);
		}
		// OFFSPRINGS bit for directories, DATALEN bit for files are the same
		if (Bitmap & DIR_BITMAP_OFFSPRINGS)
		{
			ASSERT(pFDParm1->_fdp_Flags != (DFE_FLAGS_FILE_WITH_ID | DFE_FLAGS_DIR));
			if (IsDir(pFDParm1))
			{
				// We have to combine total offspring count into the
				// FileCount field here since the API does not separate
				// them into separate file and dir offspring counts
				GETSHORT2DWORD(&pFDParm1->_fdp_FileCount, pBuffer1+Offset);
				GETSHORT2DWORD(&pFDParm2->_fdp_FileCount, pBuffer2+Offset);
				Offset += sizeof(USHORT);
			}
			else
			{
				GETDWORD2DWORD(&pFDParm1->_fdp_DataForkLen, pBuffer1+Offset);
				GETDWORD2DWORD(&pFDParm2->_fdp_DataForkLen, pBuffer2+Offset);
				Offset += sizeof(DWORD);
			}
		}
		if (Bitmap & FILE_BITMAP_RESCLEN)
		{
			ASSERT(pFDParm1->_fdp_Flags == DFE_FLAGS_FILE_WITH_ID);
			GETDWORD2DWORD(&pFDParm1->_fdp_RescForkLen, pBuffer1+Offset);
			GETDWORD2DWORD(&pFDParm2->_fdp_RescForkLen, pBuffer2+Offset);
			Offset += sizeof(DWORD);
		}

	} while (False);

	return Status;
}

/***	AfpIsCatSearchMatch
 *
 *	Given a DFE and a set of search criteria, see if this item should be
 *  returned as a match in the catalog search.
 *
 *
 *	LOCKS_ASSUMED: vds_IdDbAccessLock (SWMR, Exclusive)
 */
SHORT
AfpIsCatSearchMatch(
	IN	PDFENTRY		pDFE,
	IN	DWORD			Bitmap,			// Search criteria
	IN	DWORD			ReplyBitmap,	// Info to return
	IN	PFILEDIRPARM	pFDParm1,
	IN	PFILEDIRPARM	pFDParm2,
	IN	PUNICODE_STRING	pMatchName OPTIONAL	
)
{
	BOOLEAN		IsMatch = True;
	SHORT		Length = 0;

	PAGED_CODE();

	do
	{

		if (Bitmap & FD_BITMAP_ATTR)
		{
			FILEDIRPARM	fdp;

			fdp._fdp_Attr = pDFE->dfe_AfpAttr;
			AfpNormalizeAfpAttr(&fdp, pDFE->dfe_NtAttr);

			if ((fdp._fdp_Attr & pFDParm2->_fdp_Attr) != pFDParm1->_fdp_Attr)

			{
				IsMatch = False;
				break;
			}
		}
		if (Bitmap & FD_BITMAP_PARENT_DIRID)
		{
			if ((pDFE->dfe_Parent->dfe_AfpId < pFDParm1->_fdp_ParentId) ||
				(pDFE->dfe_Parent->dfe_AfpId > pFDParm2->_fdp_ParentId))
			{
				IsMatch = False;
				break;
			}
		}
		if (Bitmap & FD_BITMAP_CREATETIME)
		{
			if (((AFPTIME)pDFE->dfe_CreateTime < (AFPTIME)pFDParm1->_fdp_CreateTime) ||
				((AFPTIME)pDFE->dfe_CreateTime > (AFPTIME)pFDParm2->_fdp_CreateTime))
			{
				IsMatch = False;
				break;
			}
		}
		if (Bitmap & FD_BITMAP_MODIFIEDTIME)
		{
			AFPTIME	ModTime;

			ModTime = AfpConvertTimeToMacFormat(&pDFE->dfe_LastModTime);
			if ((ModTime < pFDParm1->_fdp_ModifiedTime) ||
				(ModTime > pFDParm2->_fdp_ModifiedTime))
			{
				IsMatch = False;
				break;
			}
		}
		if (Bitmap & FD_BITMAP_BACKUPTIME)
		{
			if ((pDFE->dfe_BackupTime < pFDParm1->_fdp_BackupTime) ||
				(pDFE->dfe_BackupTime > pFDParm2->_fdp_BackupTime))
			{
				IsMatch = False;
				break;
			}
		}
		if (Bitmap & FD_BITMAP_FINDERINFO)
		{
			int			i;
			PBYTE		pF, p1, p2;
			FINDERINFO 	FinderInfo;

			// NOTE: why doesn't dfe_FinderInfo.Attr1 correctly reflect the
			// Nt Hidden attribute in the first place?
			FinderInfo = pDFE->dfe_FinderInfo;
			if (pDFE->dfe_NtAttr & FILE_ATTRIBUTE_HIDDEN)
				FinderInfo.fd_Attr1 |= FINDER_FLAG_INVISIBLE;

			pF = (PBYTE) &FinderInfo;
			p1 = (PBYTE) &pFDParm1->_fdp_FinderInfo;
			p2 = (PBYTE) &pFDParm2->_fdp_FinderInfo;

			for (i = 0; i < sizeof(FINDERINFO); i++)
			{
				if ((*pF++ & *p2++) != *p1++)
				{
					IsMatch = False;
					break;	// out of for loop
				}
			}

			if (IsMatch == False)
				break;	// out of while loop
		}
		if (Bitmap & FD_BITMAP_LONGNAME)
		{
			ASSERT(ARGUMENT_PRESENT(pMatchName));

			if (pFDParm2->_fdp_fPartialName)
			{
				// Name must contain substring
				if (!AfpIsProperSubstring(&pDFE->dfe_UnicodeName, pMatchName))
				{
					IsMatch = False;
					break;
				}
			}
			else if (!EQUAL_UNICODE_STRING(&pDFE->dfe_UnicodeName, pMatchName, True))
			{
				// Whole name must match
				IsMatch = False;
				break;
			}
		}
		if (Bitmap & FILE_BITMAP_DATALEN)
		{
			// This bit is also used as DIR_BITMAP_OFFSPRINGS for directories
			if (IsDir(pFDParm1))
			{
				DWORD count;

				ASSERT(DFE_IS_DIRECTORY(pDFE) && DFE_CHILDREN_ARE_PRESENT(pDFE));

				count = pDFE->dfe_DirOffspring + pDFE->dfe_FileOffspring;

				// In this case, _fdp_FileCount holds total # of files and dirs
				if ((count < pFDParm1->_fdp_FileCount) ||
					(count > pFDParm2->_fdp_FileCount))
				{
					IsMatch = False;
					break;
				}
			}
			else
			{
				ASSERT(DFE_IS_FILE(pDFE));

				if ((pDFE->dfe_DataLen < pFDParm1->_fdp_DataForkLen) ||
					(pDFE->dfe_DataLen > pFDParm2->_fdp_DataForkLen))
				{
					IsMatch = False;
					break;
				}
			}
	
		}
		if (Bitmap & FILE_BITMAP_RESCLEN)
		{
			ASSERT(DFE_IS_FILE(pDFE));

			if ((pDFE->dfe_RescLen < pFDParm1->_fdp_RescForkLen) ||
				(pDFE->dfe_RescLen > pFDParm2->_fdp_RescForkLen))
			{
				IsMatch = False;
				break;
			}
		}
	
	} while (False);

	if (IsMatch)
	{
		Length = 2 * sizeof(BYTE);	// Struct Length plus File/Dir Flag
		if (ReplyBitmap & FD_BITMAP_PARENT_DIRID)
		{
			Length += sizeof(DWORD);
		}
		if (ReplyBitmap & FD_BITMAP_LONGNAME)
		{
			// Offset to string + pascal string length + characters
#ifdef DBCS
// FiX #11992 SFM: As a result of search, I get incorrect file information.
// 1996.09.26 V-HIDEKK
			{
			ANSI_STRING	AName;
			BYTE		NameBuf[AFP_LONGNAME_LEN+1];

			AfpInitAnsiStringWithNonNullTerm(&AName, sizeof(NameBuf), NameBuf);
			AfpConvertMungedUnicodeToAnsi(&pDFE->dfe_UnicodeName, &AName);
			Length += sizeof(USHORT) + sizeof(BYTE) + AName.Length;
			}
#else
			Length += sizeof(USHORT) + sizeof(BYTE) + pDFE->dfe_UnicodeName.Length/sizeof(WCHAR);
#endif
		}
		Length = EVENALIGN(Length);
	}

	return Length;
}