windows-nt/Source/XPSP1/NT/net/sfm/afp/server/idindex.c
2020-09-26 16:20:57 +08:00

3756 lines
97 KiB
C

/*
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,
afpDirDfeFreeBlockHead,
DIR_BLOCK_AGE_TIME,
True);
if (NT_SUCCESS(Status))
{
// Age out file and dir DFEs differently and seperately
Status = AfpScavengerScheduleEvent(afpDfeBlockAge,
afpFileDfeFreeBlockHead,
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++)
{
ASSERT (afpDirDfePartialBlockHead[i] == NULL);
ASSERT (afpDirDfeUsedBlockHead[i] == NULL);
for (pDfb = afpDirDfeFreeBlockHead[i];
pDfb != NULL;
NOTHING)
{
PDFEBLOCK pFree;
ASSERT(pDfb->dfb_NumFree == afpDfeNumDirBlocks[i]);
pFree = pDfb;
pDfb = pDfb->dfb_Next;
AfpFreeVirtualMemoryPage(pFree);
#if DBG
afpDfbAllocCount --;
#endif
}
ASSERT (afpFileDfePartialBlockHead[i] == NULL);
ASSERT (afpFileDfeUsedBlockHead[i] == NULL);
for (pDfb = afpFileDfeFreeBlockHead[i];
pDfb != NULL;)
{
PDFEBLOCK pFree;
ASSERT(pDfb->dfb_NumFree == afpDfeNumFileBlocks[i]);
pFree = pDfb;
pDfb = pDfb->dfb_Next;
AfpFreeVirtualMemoryPage(pFree);
#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;
struct _DirFileEntry **DfeDirBucketStart;
struct _DirFileEntry **DfeFileBucketStart;
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
DfeDirBucketStart = pVolDesc->vds_pDfeDirBucketStart;
DfeFileBucketStart = pVolDesc->vds_pDfeFileBucketStart;
if ((EntityMask == DFE_ANY) || (EntityMask == DFE_DIR))
{
if (EntityMask == DFE_ANY)
retry = True;
pDfEntry = DfeDirBucketStart[HASH_DIR_ID(Id,pVolDesc)];
}
else
{
pDfEntry = DfeFileBucketStart[HASH_FILE_ID(Id,pVolDesc)];
}
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 = DfeFileBucketStart[HASH_FILE_ID(Id,pVolDesc)];
}
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;
RELEASE_SPIN_LOCK(&pVolDesc->vds_VolLock, OldIrql);
if (IS_VOLUME_NTFS(pVolDesc))
{
AfpVolumeSetModifiedTime(pVolDesc);
}
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 = (USHORT)((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)
if ((RtlUnicodeStringToAnsiSize(pParent)-1) <= 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)
if ((RtlUnicodeStringToAnsiSize(pTail)-1) <= 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((ULONG_PTR)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,pVolDesc) : HASH_FILE_ID(AfpId,pVolDesc)));
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_RequiredNotifyBufLen)
{
pVolDesc->vds_RequiredNotifyBufLen = requiredLen;
}
}
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 bad 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);
// a customer hit this problem on NT4 where one of the Dfe's was NULL!
if (pDFE1 == NULL || pDFE2 == NULL)
{
ASSERT(0);
return;
}
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);
}
}
}
}
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( );
RtlZeroMemory (NameBuf, AFP_LONGNAME_LEN+1);
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);
#ifndef DBCS
// 1996.09.26 V-HIDEKK
PUTSHORT2BYTE(pBuf + Offset, pDfe->dfe_UnicodeName.Length/sizeof(WCHAR));
#endif
AfpInitAnsiStringWithNonNullTerm(&AName, sizeof(NameBuf), NameBuf);
AfpConvertMungedUnicodeToAnsi(&pDfe->dfe_UnicodeName,
&AName);
#ifdef DBCS
// FiX #11992 SFM: As a result of search, I get incorrect file information.
// 1996.09.26 V-HIDEKK
PUTSHORT2BYTE(pBuf + Offset, AName.Length);
#endif
RtlCopyMemory(pBuf + Offset + sizeof(BYTE),
NameBuf,
AName.Length);
#ifdef DBCS
// FiX #11992 SFM: As a result of search, I get incorrect file information.
// 1996.09.26 V-HIDEKK
Offset += sizeof(BYTE) + AName.Length;
#else
Offset += sizeof(BYTE) + pDfe->dfe_UnicodeName.Length/sizeof(WCHAR);
#endif
}
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((ULONG_PTR)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 ? afpDirDfePartialBlockHead[Index] : afpFileDfePartialBlockHead[Index];
if (pDfb == NULL)
{
//
// There are no partial blocks. Check if there any free ones and if there move them to partial
// since we about to allocate from them
//
if (fDir)
{
pDfb = afpDirDfeFreeBlockHead[Index];
if (pDfb != NULL)
{
AfpUnlinkDouble(pDfb, dfb_Next, dfb_Prev);
AfpLinkDoubleAtHead(afpDirDfePartialBlockHead[Index],
pDfb,
dfb_Next,
dfb_Prev);
}
}
else
{
pDfb = afpFileDfeFreeBlockHead[Index];
if (pDfb != NULL)
{
AfpUnlinkDouble(pDfb, dfb_Next, dfb_Prev);
AfpLinkDoubleAtHead(afpFileDfePartialBlockHead[Index],
pDfb,
dfb_Next,
dfb_Prev);
}
}
}
if (pDfb != NULL)
{
ASSERT(VALID_DFB(pDfb));
ASSERT((fDir && (pDfb->dfb_NumFree <= afpDfeNumDirBlocks[Index])) ||
(!fDir && (pDfb->dfb_NumFree <= afpDfeNumFileBlocks[Index])));
ASSERT (pDfb->dfb_NumFree != 0);
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
("afpAllocDfe: Found space in Block %lx\n", pDfb));
}
if (pDfb == NULL)
{
ASSERT(QUAD_SIZED(sizeof(DFEBLOCK)));
ASSERT(QUAD_SIZED(sizeof(DIRENTRY)));
ASSERT(QUAD_SIZED(sizeof(DFENTRY)));
if ((pDfb = (PDFEBLOCK)AfpAllocateVirtualMemoryPage()) != NULL)
{
LONG i;
USHORT DfeSize, UnicodeSize, MaxDfes, DirEntrySize;
#if DBG
afpDfbAllocCount ++;
#endif
UnicodeSize = afpDfeUnicodeBufSize[Index];
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_WARN,
("afpAllocDfe: No free %s blocks. Allocated a new block %lx for index %ld\n",
fDir ? "Dir" : "File", pDfb, Index));
//
// Link it in the partial list as we are about to allocate one block out of it anyway.
//
if (fDir)
{
AfpLinkDoubleAtHead(afpDirDfePartialBlockHead[Index],
pDfb,
dfb_Next,
dfb_Prev);
DfeSize = afpDfeDirBlockSize[Index];
pDfb->dfb_NumFree = MaxDfes = afpDfeNumDirBlocks[Index];
DirEntrySize = sizeof(DIRENTRY);
}
else
{
AfpLinkDoubleAtHead(afpFileDfePartialBlockHead[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;
pDfb->dfb_Age = 0;
// 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 = fDir ?
pDfEntry->dfe_pDirEntry = (PDIRENTRY)((PCHAR)pDfEntry+sizeof(DFENTRY)) : NULL;
pDfEntry->dfe_UnicodeName.Buffer = (PWCHAR)((PCHAR)pDfEntry+
DirEntrySize+
sizeof(DFENTRY));
pDfEntry->dfe_UnicodeName.MaximumLength = UnicodeSize;
}
}
else
{
DBGPRINT(DBG_COMP_CHGNOTIFY, DBG_LEVEL_ERR,
("afpAllocDfe: AfpAllocateVirtualMemoryPage failed\n"));
AFPLOG_ERROR(AFPSRVMSG_VIRTMEM_ALLOC_FAILED,
STATUS_INSUFFICIENT_RESOURCES,
NULL,
0,
NULL);
}
}
if (pDfb != NULL)
{
PDFEBLOCK pTmp;
ASSERT(VALID_DFB(pDfb));
pDfEntry = pDfb->dfb_FreeHead;
ASSERT(VALID_DFE(pDfEntry));
ASSERT(pDfb->dfb_fDir ^ (pDfEntry->dfe_pDirEntry == NULL));
#if DBG
afpDfeAllocCount ++;
#endif
pDfb->dfb_FreeHead = pDfEntry->dfe_NextFree;
pDfb->dfb_NumFree --;
//
// If the block is now empty (completely used), unlink it from here and move it
// to the Used list.
//
if (pDfb->dfb_NumFree == 0)
{
AfpUnlinkDouble(pDfb, dfb_Next, dfb_Prev);
if (fDir)
{
AfpLinkDoubleAtHead(afpDirDfeUsedBlockHead[Index],
pDfb,
dfb_Next,
dfb_Prev);
}
else
{
AfpLinkDoubleAtHead(afpFileDfeUsedBlockHead[Index],
pDfb,
dfb_Next,
dfb_Prev);
}
}
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
// 64K blocks and also that these are allocated *at* 64K boundaries
// This lets us *cheaply* get to the owning DFB from the DFE.
pDfb = (PDFEBLOCK)((ULONG_PTR)pDfEntry & ~(PAGE_SIZE-1));
ASSERT(VALID_DFB(pDfb));
ASSERT(pDfb->dfb_fDir ^ (pDfEntry->dfe_pDirEntry == NULL));
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 (pDfb->dfb_NumFree == 1)
{
LONG Index;
//
// The block is now partially free (it used to be completely used). move it to the partial list.
//
Index = USIZE_TO_INDEX(pDfEntry->dfe_UnicodeName.MaximumLength);
AfpUnlinkDouble(pDfb, dfb_Next, dfb_Prev);
if (pDfb->dfb_fDir)
{
AfpLinkDoubleAtHead(afpDirDfePartialBlockHead[Index],
pDfb,
dfb_Next,
dfb_Prev);
}
else
{
AfpLinkDoubleAtHead(afpFileDfePartialBlockHead[Index],
pDfb,
dfb_Next,
dfb_Prev);
}
}
else if (pDfb->dfb_NumFree == NumBlks)
{
LONG Index;
//
// The block is now completely free (used to be partially used). move it to the free list
//
Index = USIZE_TO_INDEX(pDfEntry->dfe_UnicodeName.MaximumLength);
pDfb->dfb_Age = 0;
AfpUnlinkDouble(pDfb, dfb_Next, dfb_Prev);
if (AfpServerState == AFP_STATE_STOP_PENDING)
{
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_WARN,
("afpFreeDfe: Freeing Block %lx\n", pDfb));
AfpFreeVirtualMemoryPage(pDfb);
#if DBG
afpDfbAllocCount --;
#endif
}
else
{
if (pDfb->dfb_fDir)
{
AfpLinkDoubleAtHead(afpDirDfeFreeBlockHead[Index],
pDfb,
dfb_Next,
dfb_Prev);
}
else
{
AfpLinkDoubleAtHead(afpFileDfeFreeBlockHead[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;
ASSERT (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);
AfpFreeVirtualMemoryPage(pFree);
#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,
OUT BOOLEAN *pfNewVolume,
OUT BOOLEAN *pfVerifyIndex
)
{
NTSTATUS Status;
ULONG CreateInfo;
FILESYSHANDLE fshIdDb;
IDDBHDR IdDbHdr;
BOOLEAN fLogEvent=FALSE;
PAGED_CODE( );
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_INFO,
("AfpInitIdDb: Initializing Id Database...\n"));
*pfNewVolume = FALSE;
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))
{
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_ERR,
("AfpInitIdDb: AfpIoCreate failed with %lx\n", Status));
ASSERT(0);
fLogEvent = TRUE;
break;
}
if (CreateInfo == FILE_OPENED)
{
// read in the existing header. If we fail, just start from scratch
Status = afpReadIdDb(pVolDesc, &fshIdDb, pfVerifyIndex);
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);
*pfNewVolume = TRUE;
}
else if (CreateInfo != FILE_OPENED)
{
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_ERR,
("AfpInitIdDb: unexpected create action 0x%lx\n", CreateInfo));
ASSERTMSG("Unexpected Create Action\n", 0); // this should never happen
fLogEvent = TRUE;
Status = STATUS_UNSUCCESSFUL;
}
AfpIoClose(&fshIdDb);
//
// write back the IdDb header to the file, but with bad signature.
// If server shuts down, the correct signature will be
// written. If macfile is closed down using "net stop macfile"
// signature is corrupted with a different type
// If cool boot/bugcheck, a third type
// During volume startup, we will know from the signature,
// whether to rebuild completely, read iddb but verify or
// not rebuild at all
//
if (NT_SUCCESS(Status))
{
DBGPRINT(DBG_COMP_IDINDEX, DBG_LEVEL_ERR,
("AfpInitIdDb: ***** Corrupting IDDB header ***** \n"));
AfpVolDescToIdDbHdr(pVolDesc, &IdDbHdr);
IdDbHdr.idh_Signature = AFP_SERVER_SIGNATURE_INITIDDB;
AfpVolumeUpdateIdDbAndDesktop(pVolDesc,FALSE,FALSE,&IdDbHdr);
}
}
else
{
// its CDFS, just initialize the memory image of the IdDB
Status = afpSeedIdDb(pVolDesc);
*pfNewVolume = TRUE;
}
} while (False);
if (fLogEvent)
{
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;
}
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
)
{
DWORD i;
struct _DirFileEntry ** DfeDirBucketStart;
struct _DirFileEntry ** DfeFileBucketStart;
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;
AfpSwmrAcquireExclusive(&pVolDesc->vds_IdDbAccessLock);
DfeFileBucketStart = pVolDesc->vds_pDfeFileBucketStart;
if (DfeFileBucketStart)
{
for (i = 0; i < pVolDesc->vds_FileHashTableSize; i++)
{
for (pDfEntry = DfeFileBucketStart[i];
pDfEntry != NULL;
NOTHING)
{
ASSERT(VALID_DFE(pDfEntry));
pFree = pDfEntry;
pDfEntry = pDfEntry->dfe_NextOverflow;
FREE_DFE(pFree);
}
DfeFileBucketStart[i] = NULL;
}
}
DfeDirBucketStart = pVolDesc->vds_pDfeDirBucketStart;
if (DfeDirBucketStart)
{
for (i = 0; i < pVolDesc->vds_DirHashTableSize; i++)
{
for (pDfEntry = DfeDirBucketStart[i];
pDfEntry != NULL;
NOTHING)
{
ASSERT(VALID_DFE(pDfEntry));
pFree = pDfEntry;
pDfEntry = pDfEntry->dfe_NextOverflow;
FREE_DFE(pFree);
}
DfeDirBucketStart[i] = NULL;
}
}
RtlZeroMemory(pVolDesc->vds_pDfeCache,
IDINDEX_CACHE_ENTRIES * sizeof(PDFENTRY));
AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock);
}
}
/*** 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);
}
}
LONG afpVirtualMemoryCount = 0;
LONG afpVirtualMemorySize = 0;
/*** AfpAllocVirtualMemory
*
* This is a wrapper over NtAllocateVirtualMemory.
*/
PBYTE FASTCALL
AfpAllocateVirtualMemoryPage(
IN VOID
)
{
PBYTE pMem = NULL;
NTSTATUS Status;
PBLOCK64K pCurrBlock;
PBLOCK64K pTmpBlk;
SIZE_T Size64K;
DWORD i, dwMaxPages;
Size64K = BLOCK_64K_ALLOC;
dwMaxPages = (BLOCK_64K_ALLOC/PAGE_SIZE);
pCurrBlock = afp64kBlockHead;
//
// if we have never allocated a 64K block as yet, or we don't have one that
// has any free page(s) in it, allocate a new block!
//
if ((pCurrBlock == NULL) || (pCurrBlock->b64_PagesFree == 0))
{
pCurrBlock = (PBLOCK64K)AfpAllocNonPagedMemory(sizeof(BLOCK64K));
if (pCurrBlock == NULL)
{
return(NULL);
}
ExInterlockedIncrementLong(&afpVirtualMemoryCount, &AfpStatisticsLock);
ExInterlockedAddUlong(&afpVirtualMemorySize, (ULONG)Size64K, &(AfpStatisticsLock.SpinLock));
Status = NtAllocateVirtualMemory(NtCurrentProcess(),
&pMem,
0L,
&Size64K,
MEM_COMMIT,
PAGE_READWRITE);
if (NT_SUCCESS(Status))
{
ASSERT(pMem != NULL);
#if DBG
afpDfe64kBlockCount++;
#endif
pCurrBlock->b64_Next = afp64kBlockHead;
pCurrBlock->b64_BaseAddress = pMem;
pCurrBlock->b64_PagesFree = dwMaxPages;
for (i=0; i<dwMaxPages; i++)
{
pCurrBlock->b64_PageInUse[i] = FALSE;
}
afp64kBlockHead = pCurrBlock;
}
else
{
AfpFreeMemory(pCurrBlock);
return(NULL);
}
}
//
// if we came this far, we are guaranteed that pCurrBlock is pointing to a
// block that has at least one page free
//
ASSERT ((pCurrBlock != NULL) &&
(pCurrBlock->b64_PagesFree > 0) &&
(pCurrBlock->b64_PagesFree <= dwMaxPages));
// find out which page is free
for (i=0; i<dwMaxPages; i++)
{
if (pCurrBlock->b64_PageInUse[i] == FALSE)
{
break;
}
}
ASSERT(i < dwMaxPages);
pCurrBlock->b64_PagesFree--;
pCurrBlock->b64_PageInUse[i] = TRUE;
pMem = ((PBYTE)pCurrBlock->b64_BaseAddress) + (i * PAGE_SIZE);
//
// if this 64kblock has no more free pages in it, move it to a spot after
// all the blocks that have some pages free in it. For that, we first
// find the guy who has no pages free in him and move this block after him
//
if (pCurrBlock->b64_PagesFree == 0)
{
pTmpBlk = pCurrBlock->b64_Next;
if (pTmpBlk != NULL)
{
while (1)
{
// found a guy who has no free page in it?
if (pTmpBlk->b64_PagesFree == 0)
{
break;
}
// is this the last guy on the list?
if (pTmpBlk->b64_Next == NULL)
{
break;
}
pTmpBlk = pTmpBlk->b64_Next;
}
}
// if we found a block
if (pTmpBlk)
{
ASSERT(afp64kBlockHead == pCurrBlock);
afp64kBlockHead = pCurrBlock->b64_Next;
pCurrBlock->b64_Next = pTmpBlk->b64_Next;
pTmpBlk->b64_Next = pCurrBlock;
}
}
return pMem;
}
VOID FASTCALL
AfpFreeVirtualMemoryPage(
IN PVOID pBuffer
)
{
NTSTATUS Status;
PBYTE BaseAddr;
PBLOCK64K pCurrBlock;
PBLOCK64K pPrevBlk;
SIZE_T Size64K;
DWORD i, dwMaxPages, dwPageNum, Offset;
dwMaxPages = (BLOCK_64K_ALLOC/PAGE_SIZE);
Size64K = BLOCK_64K_ALLOC;
pCurrBlock = afp64kBlockHead;
pPrevBlk = afp64kBlockHead;
BaseAddr = (PBYTE)((ULONG_PTR)pBuffer & ~(BLOCK_64K_ALLOC - 1));
Offset = (DWORD)(((PBYTE)pBuffer - BaseAddr));
dwPageNum = Offset/PAGE_SIZE;
ASSERT(Offset < BLOCK_64K_ALLOC);
while (pCurrBlock != NULL)
{
if (pCurrBlock->b64_BaseAddress == BaseAddr)
{
break;
}
pPrevBlk = pCurrBlock;
pCurrBlock = pCurrBlock->b64_Next;
}
ASSERT(pCurrBlock->b64_BaseAddress == BaseAddr);
pCurrBlock->b64_PageInUse[dwPageNum] = FALSE;
pCurrBlock->b64_PagesFree++;
//
// if all the pages in this block are unused, then it's time to free this block
// after removing from the list
//
if (pCurrBlock->b64_PagesFree == dwMaxPages)
{
// is our guy the first (and potentially the only one) on the list?
if (afp64kBlockHead == pCurrBlock)
{
afp64kBlockHead = pCurrBlock->b64_Next;
}
// nope, there are others and we're somewhere in the middle (or end)
else
{
pPrevBlk->b64_Next = pCurrBlock->b64_Next;
}
AfpFreeMemory(pCurrBlock);
ExInterlockedDecrementLong(&afpVirtualMemoryCount, &AfpStatisticsLock);
ExInterlockedAddUlong(&afpVirtualMemorySize,
-1*((ULONG)Size64K),
&(AfpStatisticsLock.SpinLock));
Status = NtFreeVirtualMemory(NtCurrentProcess(),
(PVOID *)&BaseAddr,
&Size64K,
MEM_RELEASE);
#if DBG
ASSERT(afpDfe64kBlockCount > 0);
afpDfe64kBlockCount--;
#endif
}
//
// if a page became available in this block for the first time, move this
// block to the front of the list (unless it already is there)
//
else if (pCurrBlock->b64_PagesFree == 1)
{
if (afp64kBlockHead != pCurrBlock)
{
pPrevBlk->b64_Next = pCurrBlock->b64_Next;
pCurrBlock->b64_Next = afp64kBlockHead;
afp64kBlockHead = pCurrBlock;
}
}
}
#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