|
|
/*
Copyright (c) 1992 Microsoft Corporation
Module Name:
desktop.c
Abstract:
This module contains the routines for manipulating the desktop database.
Author:
Jameel Hyder (microsoft!jameelh)
Revision History: 25 Apr 1992 Initial Version
Notes: Tab stop: 4 --*/
#define FILENUM FILE_DESKTOP
#define DESKTOP_LOCALS
#include <afp.h>
#include <scavengr.h>
#include <fdparm.h>
#include <pathmap.h>
#include <client.h>
#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, AfpDesktopInit)
#pragma alloc_text( PAGE, AfpAddIcon)
#pragma alloc_text( PAGE, AfpLookupIcon)
#pragma alloc_text( PAGE, AfpLookupIconInfo)
#pragma alloc_text( PAGE, AfpAddAppl)
#pragma alloc_text( PAGE, AfpLookupAppl)
#pragma alloc_text( PAGE, AfpRemoveAppl)
#pragma alloc_text( PAGE, AfpAddComment)
#pragma alloc_text( PAGE, AfpGetComment)
#pragma alloc_text( PAGE, AfpRemoveComment)
#pragma alloc_text( PAGE, AfpAddIconToGlobalList)
#pragma alloc_text( PAGE, afpLookupIconInGlobalList)
#pragma alloc_text( PAGE, AfpFreeGlobalIconList)
#pragma alloc_text( PAGE, afpGetGlobalIconInfo)
#pragma alloc_text( PAGE, afpReadDesktopFromDisk)
#pragma alloc_text( PAGE, AfpInitDesktop)
#pragma alloc_text( PAGE, AfpUpdateDesktop)
#pragma alloc_text( PAGE, AfpFreeDesktopTables)
#endif
/*** AfpDesktopInit
* * Initialize locks for global icons. */ NTSTATUS AfpDesktopInit( VOID ) { AfpSwmrInitSwmr(&AfpIconListLock);
return STATUS_SUCCESS; }
/*** AfpAddIcon
* * Add an icon to the desktop database. The icon is added in such a way that * the list is maintained in a sorted fashion - sorted by Creator, Type and * IconType * * LOCKS: vds_DtAccessLock (SWMR, Exclusive); */ AFPSTATUS AfpAddIcon( IN PVOLDESC pVolDesc, // Volume descriptor of referenced desktop
IN DWORD Creator, IN DWORD Type, IN DWORD Tag, IN LONG IconSize, IN DWORD IconType, IN PBYTE pIcon // The icon bitmap
) { PICONINFO pIconInfo; PICONINFO * ppIconInfo; BOOLEAN Found = False; AFPSTATUS Status = AFP_ERR_NONE;
PAGED_CODE( );
AfpSwmrAcquireExclusive(&pVolDesc->vds_DtAccessLock); ppIconInfo = &pVolDesc->vds_pIconBuckets[HASH_ICON(Creator)]; do { // Find the right slot
for (;(pIconInfo = *ppIconInfo) != NULL; ppIconInfo = &pIconInfo->icon_Next) { if (pIconInfo->icon_Creator < Creator) continue; if (pIconInfo->icon_Creator > Creator) break; if (pIconInfo->icon_Type < Type) continue; if (pIconInfo->icon_Type > Type) break; if (pIconInfo->icon_IconType < (USHORT)IconType) continue; if (pIconInfo->icon_IconType > (USHORT)IconType) break; /*
* If we come this far, we have hit the bulls eye * Make sure the size matches, before we commit */ if (pIconInfo->icon_Size != IconSize) { Status = AFP_ERR_ICON_TYPE; break; } Found = True; break; }
if (!Found && (Status == AFP_ERR_NONE)) { // ppIconInfo now points to the right place
if ((pIconInfo = ALLOC_ICONINFO(IconSize)) != NULL) { pIconInfo->icon_Next = *ppIconInfo; *ppIconInfo = pIconInfo; pIconInfo->icon_Creator = Creator; pIconInfo->icon_Type = Type; pIconInfo->icon_IconType = (USHORT)IconType; pIconInfo->icon_Size = (SHORT)IconSize; pIconInfo->icon_Tag = Tag; pVolDesc->vds_cIconEnts ++; Found = True; } else Status = AFP_ERR_MISC; } if (Found && (Status == AFP_ERR_NONE)) { RtlCopyMemory((PBYTE)pIconInfo + sizeof(ICONINFO), pIcon, IconSize); } } while (False); AfpSwmrRelease(&pVolDesc->vds_DtAccessLock); return Status; }
/*** AfpLookupIcon
* * Search the desktop for an icon matching the given search parameters. * * LOCKS: vds_DtAccessLock (SWMR, Shared), AfpIconListLock (SWMR, Shared) */ AFPSTATUS AfpLookupIcon( IN PVOLDESC pVolDesc, // Volume descriptor of referenced desktop
IN DWORD Creator, IN DWORD Type, IN LONG Length, IN DWORD IconType, OUT PLONG pActualLength, OUT PBYTE pIconBitMap // Buffer for icon bit map
) { PICONINFO pIconInfo; LONG LengthToCopy; AFPSTATUS Status = AFP_ERR_NONE;
PAGED_CODE( );
LengthToCopy = Length;
AfpSwmrAcquireShared(&pVolDesc->vds_DtAccessLock); pIconInfo = pVolDesc->vds_pIconBuckets[HASH_ICON(Creator)];
// Scan the list looking for the entry
for (;pIconInfo != NULL; pIconInfo = pIconInfo->icon_Next) { if (pIconInfo->icon_Creator < Creator) continue; if (pIconInfo->icon_Creator > Creator) { pIconInfo = NULL; break; } if (pIconInfo->icon_Type < Type) continue; if (pIconInfo->icon_Type > Type) { pIconInfo = NULL; break; } if (pIconInfo->icon_IconType < (USHORT)IconType) continue; if (pIconInfo->icon_IconType > (USHORT)IconType) { pIconInfo = NULL; break; } break; } // If we did not find it, try the global list
if (pIconInfo == NULL) { Status = afpLookupIconInGlobalList(Creator, Type, IconType, &LengthToCopy, pIconBitMap); } else if (Length > 0) { if ((LONG)(pIconInfo->icon_Size) < Length) { LengthToCopy = (LONG)(pIconInfo->icon_Size); } else { LengthToCopy = Length; } RtlCopyMemory(pIconBitMap, (PBYTE)pIconInfo + sizeof(ICONINFO), LengthToCopy); }
AfpSwmrRelease(&pVolDesc->vds_DtAccessLock);
*pActualLength = LengthToCopy; return Status; }
/*** AfpLookupIconInfo
* * Search the desktop for an icon matching the given Creator. In case of * multiple icons corresponding to the same creator, get the nth where n * is the index. * * LOCKS: vds_DtAccessLock (SWMR, Shared), AfpIconListLock (SWMR, Shared) */ AFPSTATUS AfpLookupIconInfo( IN PVOLDESC pVolDesc, // Volume descriptor of referenced desktop
IN DWORD Creator, // Creator associated with the icon
IN LONG Index, // Index number of Icon
OUT PDWORD pType, // Place where Type is returned
OUT PDWORD pIconType, // Icon type e.g. ICN#
OUT PDWORD pTag, // Arbitrary tag
OUT PLONG pSize // Size of the icon
) { PICONINFO pIconInfo; LONG i; AFPSTATUS Status = AFP_ERR_ITEM_NOT_FOUND;
PAGED_CODE( );
AfpSwmrAcquireShared(&pVolDesc->vds_DtAccessLock); pIconInfo = pVolDesc->vds_pIconBuckets[HASH_ICON(Creator)];
// Scan the list looking for the first entry
for (;pIconInfo != NULL; pIconInfo = pIconInfo->icon_Next) { if (pIconInfo->icon_Creator == Creator) break; // Found the first one
if (pIconInfo->icon_Creator > Creator) { pIconInfo = NULL; break; } }
/*
* We are now either pointing to the first entry or there are none. In the * latter case, we just fall through */ for (i = 1; pIconInfo != NULL; pIconInfo = pIconInfo->icon_Next) { if ((pIconInfo->icon_Creator > Creator) || (i > Index)) { pIconInfo = NULL; break; }
if (i == Index) break; // Found the right entry
i++; }
// If we did find it, extract the information
if (pIconInfo != NULL) { *pSize = pIconInfo->icon_Size; *pType = pIconInfo->icon_Type; *pTag = pIconInfo->icon_Tag; *pIconType = pIconInfo->icon_IconType; Status = AFP_ERR_NONE; }
// If we did not find it, try the global list, but only for the first one
else if (Index == 1) { Status = afpGetGlobalIconInfo(Creator, pType, pIconType, pTag, pSize); } else Status = AFP_ERR_ITEM_NOT_FOUND;
AfpSwmrRelease(&pVolDesc->vds_DtAccessLock); return Status; }
/*** AfpAddAppl
* * Add an APPL mapping to the desktop database. Is added in such a way that * the list is maintained in a sorted fashion - sorted by Creator. It is * already determined that the application file exists and that the user has * appropriate access to it. * * LOCKS: vds_DtAccessLock (SWMR, Exclusive); */ AFPSTATUS AfpAddAppl( IN PVOLDESC pVolDesc, // Volume descriptor of referenced desktop
IN DWORD Creator, IN DWORD ApplTag, IN DWORD FileNum, // File number of the associated file
IN BOOLEAN Internal, // Is the server adding the APPL itself?
IN DWORD ParentID // DirId of parent dir of the application file
) { PAPPLINFO2 pApplInfo, *ppApplInfo; BOOLEAN ApplReplace = False, UpdateDT = True; AFPSTATUS Status = AFP_ERR_NONE;
PAGED_CODE( );
ASSERT(FileNum != 0);
AfpSwmrAcquireExclusive(&pVolDesc->vds_DtAccessLock);
ppApplInfo = &pVolDesc->vds_pApplBuckets[HASH_APPL(Creator)];
// Find the right slot
for (;(pApplInfo = *ppApplInfo) != NULL; ppApplInfo = &pApplInfo->appl_Next) { if (pApplInfo->appl_Creator >= Creator) break; }
/*
* If there is already an entry for this creator, make sure it is not for * the same file, if it is replace it. */ for ( ; pApplInfo != NULL && pApplInfo->appl_Creator == Creator; pApplInfo = pApplInfo->appl_Next) { if (pApplInfo->appl_FileNum == FileNum) { if (!Internal) { pApplInfo->appl_Tag = ApplTag; } else { if (pApplInfo->appl_ParentID == ParentID) UpdateDT = False; }
pApplInfo->appl_ParentID = ParentID; ApplReplace = True; } }
if (!ApplReplace) { // ppApplInfo now points to the right place
if ((pApplInfo = ALLOC_APPLINFO()) != NULL) { pApplInfo->appl_Next = *ppApplInfo; *ppApplInfo = pApplInfo; pApplInfo->appl_Creator = Creator; pApplInfo->appl_Tag = ApplTag; pApplInfo->appl_FileNum = FileNum; pApplInfo->appl_ParentID = ParentID; pVolDesc->vds_cApplEnts ++; } else Status = AFP_ERR_MISC; }
AfpSwmrRelease(&pVolDesc->vds_DtAccessLock);
return Status; }
/*** AfpLookupAppl
* * Search the desktop for an appl entry matching the given Creator. In * case of multiple appl entries corresponding to the same creator, get * the nth where n is the index. * * LOCKS: vds_DtAccessLock (SWMR, Shared); */ AFPSTATUS AfpLookupAppl( IN PVOLDESC pVolDesc, // Volume descriptor of referenced desktop
IN DWORD Creator, IN LONG Index, OUT PDWORD pApplTag, // Place holder for Tag
OUT PDWORD pFileNum, // Place holder for file number
OUT PDWORD pParentID ) { PAPPLINFO2 pApplInfo; AFPSTATUS Status = AFP_ERR_NONE; LONG i;
PAGED_CODE( );
AfpSwmrAcquireShared(&pVolDesc->vds_DtAccessLock); pApplInfo = pVolDesc->vds_pApplBuckets[HASH_ICON(Creator)];
// Scan the list looking for the entry
for (;pApplInfo != NULL; pApplInfo = pApplInfo->appl_Next) { if (pApplInfo->appl_Creator == Creator) break; if (pApplInfo->appl_Creator > Creator) { pApplInfo = NULL; break; } } /*
* We are now either pointing to the first entry or there are none. In the * latter case, we just fall through */ if (Index != 0) { for (i = 1; pApplInfo!=NULL; i++, pApplInfo = pApplInfo->appl_Next) { if ((i > Index) || (pApplInfo->appl_Creator != Creator)) { pApplInfo = NULL; break; } if (i == Index) break; // Found the right entry
} } if (pApplInfo == NULL) Status = AFP_ERR_ITEM_NOT_FOUND; else { *pFileNum = pApplInfo->appl_FileNum; *pApplTag = pApplInfo->appl_Tag; *pParentID = pApplInfo->appl_ParentID; } AfpSwmrRelease(&pVolDesc->vds_DtAccessLock); return Status; }
/*** AfpRemoveAppl
* * The entries corresponding to the given Creator in the specified directory * is removed from the desktop database. It is already determined that the * application file exists and that the user has appropriate access to it. * * LOCKS: vds_DtAccessLock (SWMR, Exclusive); */ AFPSTATUS AfpRemoveAppl( IN PVOLDESC pVolDesc, // Open Volume descriptor of ref desktop
IN DWORD Creator, IN DWORD FileNum // File number of the associated file
) { PAPPLINFO2 pApplInfo, *ppApplInfo; AFPSTATUS Status = AFP_ERR_NONE; BOOLEAN Found = False;
PAGED_CODE( );
AfpSwmrAcquireExclusive(&pVolDesc->vds_DtAccessLock); ppApplInfo = &pVolDesc->vds_pApplBuckets[HASH_APPL(Creator)];
// Find the APPL entry in the desktop
for (;(pApplInfo = *ppApplInfo) != NULL; ppApplInfo = &pApplInfo->appl_Next) { if (pApplInfo->appl_Creator < Creator) continue; if (pApplInfo->appl_Creator > Creator) break; /*
* Check if the File number matches, if it does delete. */ if (pApplInfo->appl_FileNum == FileNum) { Found = True; *ppApplInfo = pApplInfo->appl_Next; AfpFreeMemory(pApplInfo); pVolDesc->vds_cApplEnts --; break; } } if (!Found) Status = AFP_ERR_ITEM_NOT_FOUND;
AfpSwmrRelease(&pVolDesc->vds_DtAccessLock); return Status; }
/*** AfpAddComment
* * Add the comment to the file or directory in question. Create the comment * stream on the entity in question (if it does not already exist), convert * the comment to unicode and write it. Update the flag in the DFEntry. */ AFPSTATUS AfpAddComment( IN PSDA pSda, // Session Data Area
IN PVOLDESC pVolDesc, // Volume descriptor of referenced desktop
IN PANSI_STRING Comment, // Comment to associate with the file/dir
IN PPATHMAPENTITY pPME, // Handle to the entity or its Host Id
IN BOOLEAN Directory, // True if directory
IN DWORD AfpId ) { UNICODE_STRING UComment; WCHAR CommentBuf[AFP_MAXCOMMENTSIZE+1]; FILESYSHANDLE HandleCommentStream; DWORD CreateInfo; NTSTATUS Status = AFP_ERR_MISC; PDFENTRY pDFE = NULL; BOOLEAN RestoreModTime = FALSE; AFPTIME aModTime; TIME ModTime;
PAGED_CODE( );
ASSERT (IS_VOLUME_NTFS(pVolDesc));
if (Comment->Length == 0) { AfpRemoveComment(pSda, pVolDesc, pPME, Directory, AfpId); return AFP_ERR_NONE; }
if (Comment->Length > AFP_MAXCOMMENTSIZE) { // Truncate comment if necessary
Comment->Length = AFP_MAXCOMMENTSIZE; }
UComment.Buffer = CommentBuf; UComment.MaximumLength = (USHORT)(RtlAnsiStringToUnicodeSize(Comment) + sizeof(WCHAR)); UComment.Length = 0;
AfpConvertStringToUnicode(Comment, &UComment);
do { AfpImpersonateClient(pSda);
// Get the last modified time from the file so we can reset it.
Status = AfpIoQueryTimesnAttr( &pPME->pme_Handle, NULL, &ModTime, NULL );
if (NT_SUCCESS(Status)) { RestoreModTime = TRUE; aModTime = AfpConvertTimeToMacFormat(&ModTime); }
// Open the comment stream on the target entity.
Status = AfpIoCreate(&pPME->pme_Handle, AFP_STREAM_COMM, &UNullString, FILEIO_ACCESS_WRITE, FILEIO_DENY_NONE, FILEIO_OPEN_FILE, FILEIO_CREATE_HARD, FILE_ATTRIBUTE_NORMAL, True, NULL, &HandleCommentStream, &CreateInfo, NULL, NULL, NULL);
AfpRevertBack();
if (Status != AFP_ERR_NONE) { if ((Status = AfpIoConvertNTStatusToAfpStatus(Status)) != AFP_ERR_ACCESS_DENIED) Status = AFP_ERR_MISC; break; }
Status = AfpIoWrite(&HandleCommentStream, &LIZero, (LONG)UComment.Length, (PBYTE)UComment.Buffer);
AfpIoClose(&HandleCommentStream);
if( RestoreModTime ) { AfpIoSetTimesnAttr( &pPME->pme_Handle, NULL, &aModTime, 0, 0, NULL, NULL ); }
if (NT_SUCCESS(Status)) { AfpVolumeSetModifiedTime(pVolDesc);
AfpSwmrAcquireExclusive(&pVolDesc->vds_IdDbAccessLock); if ((pDFE = AfpFindDfEntryById(pVolDesc, AfpId, DFE_ANY)) != NULL) { pDFE->dfe_Flags |= DFE_FLAGS_HAS_COMMENT; } else { Status = AFP_ERR_MISC; } AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock); } } while (False);
return Status; }
/*** AfpGetComment
* * Extract the comment from the file or directory in question. The comment is * copied to the ReplyBuf. */ AFPSTATUS AfpGetComment( IN PSDA pSda, // Session Data Area
IN PVOLDESC pVolDesc, // Volume descriptor of referenced desktop
IN PPATHMAPENTITY pPME, // Handle to the entity or its Host Id
IN BOOLEAN Directory // True if directory
) { NTSTATUS Status = AFP_ERR_MISC; LONG SizeRead; UNICODE_STRING UComment; WCHAR CommentBuf[AFP_MAXCOMMENTSIZE+1]; ANSI_STRING AComment; FILESYSHANDLE HandleCommentStream;
PAGED_CODE( );
// ASSERT (IS_VOLUME_NTFS(pVolDesc));
// Initialize AComment
AComment.Buffer = pSda->sda_ReplyBuf + 1; // For size of string
AComment.MaximumLength = AFP_MAXCOMMENTSIZE; AComment.Length = 0;
UComment.MaximumLength = (AFP_MAXCOMMENTSIZE + 1) * sizeof(WCHAR); UComment.Buffer = CommentBuf;
do { AfpImpersonateClient(pSda);
// Open the comment stream on the target entity.
Status = AfpIoOpen(&pPME->pme_Handle, AFP_STREAM_COMM, FILEIO_OPEN_FILE, &UNullString, FILEIO_ACCESS_READ, FILEIO_DENY_NONE, True, &HandleCommentStream);
AfpRevertBack();
if (Status != AFP_ERR_NONE) { Status = AfpIoConvertNTStatusToAfpStatus(Status); if (Status == AFP_ERR_OBJECT_NOT_FOUND) Status = AFP_ERR_ITEM_NOT_FOUND; else if (Status != AFP_ERR_ACCESS_DENIED) Status = AFP_ERR_OBJECT_NOT_FOUND; break; }
Status = AfpIoRead(&HandleCommentStream, &LIZero, (LONG)UComment.MaximumLength, &SizeRead, (PBYTE)UComment.Buffer);
AfpIoClose(&HandleCommentStream);
if (Status == AFP_ERR_NONE) { UComment.Length = (USHORT) SizeRead; AfpConvertStringToAnsi(&UComment, &AComment); pSda->sda_ReplyBuf[0] = (BYTE)AComment.Length; pSda->sda_ReplySize = AComment.Length + 1; } } while (False);
return Status; }
/*** AfpRemoveComment
* * Remove the comment from the file or directory in question. Essentially * open the comment stream and set the length to 0. */ AFPSTATUS AfpRemoveComment( IN PSDA pSda, // Session Data Area
IN PVOLDESC pVolDesc, // Volume descriptor of referenced desktop
IN PPATHMAPENTITY pPME, // Handle to the entity or its Host Id
IN BOOLEAN Directory, // True if directory
IN DWORD AfpId ) { FILESYSHANDLE HandleCommentStream; NTSTATUS Status = AFP_ERR_MISC; PDFENTRY pDFE = NULL;
PAGED_CODE( );
ASSERT (IS_VOLUME_NTFS(pVolDesc));
do { AfpImpersonateClient(pSda);
// Open the comment stream on the target entity.
Status = AfpIoOpen(&pPME->pme_Handle, AFP_STREAM_COMM, FILEIO_OPEN_FILE, &UNullString, FILEIO_ACCESS_DELETE, FILEIO_DENY_NONE, True, &HandleCommentStream);
AfpRevertBack();
if (Status != AFP_ERR_NONE) { if ((Status = AfpIoConvertNTStatusToAfpStatus(Status)) != AFP_ERR_ACCESS_DENIED) Status = AFP_ERR_ITEM_NOT_FOUND; break; } Status = AfpIoMarkFileForDelete(&HandleCommentStream, NULL, NULL, NULL);
AfpIoClose(&HandleCommentStream);
if (NT_SUCCESS(Status)) { AfpVolumeSetModifiedTime(pVolDesc);
AfpSwmrAcquireExclusive(&pVolDesc->vds_IdDbAccessLock); if ((pDFE = AfpFindDfEntryById(pVolDesc, AfpId, DFE_ANY)) != NULL) { pDFE->dfe_Flags &= ~DFE_FLAGS_HAS_COMMENT; } else { Status = AFP_ERR_MISC; } AfpSwmrRelease(&pVolDesc->vds_IdDbAccessLock); } } while (False);
return Status; }
/*** AfpAddIconToGlobalList
* * The global list of icons is a server maintained list updated by the service. * This adds an icon to the list. If an icon exists for the given type and * creator, it is replaced. This list is maintained via the AfpIconAdd() admin * api. * * LOCKS: AfpIconListLock (SWMR, Exclusive); */ AFPSTATUS AfpAddIconToGlobalList( IN DWORD Type, IN DWORD Creator, IN DWORD IconType, IN LONG IconSize, IN PBYTE pIconBitMap ) { PICONINFO pIconInfo, pIconInfoNew, *ppIconInfo; AFPSTATUS Status = AFP_ERR_NONE;
PAGED_CODE( );
// Pre-allocate memory for the new icon, delete if necessary later
if ((pIconInfoNew = ALLOC_ICONINFO(IconSize)) == NULL) return AFP_ERR_MISC;
AfpSwmrAcquireExclusive(&AfpIconListLock); ppIconInfo = &AfpGlobalIconList; for (; (pIconInfo = *ppIconInfo) != NULL; ppIconInfo = &pIconInfo->icon_Next) { if ((pIconInfo->icon_Type == Type) && (pIconInfo->icon_Creator == Creator)) break; } if (pIconInfo == NULL) { if (IconSize > 0) RtlCopyMemory((PBYTE)pIconInfoNew + sizeof(ICONINFO), pIconBitMap, IconSize); pIconInfoNew->icon_Creator = Creator; pIconInfoNew->icon_Type = Type; pIconInfoNew->icon_IconType = (USHORT)IconType; pIconInfoNew->icon_Size = (SHORT)IconSize; pIconInfoNew->icon_Tag = 0; pIconInfoNew->icon_Next = NULL; *ppIconInfo = pIconInfoNew; } else { // We do not need the memory any more, release it
AfpFreeMemory(pIconInfoNew); if (pIconInfo->icon_IconType != (USHORT)IconType) Status = AFPERR_InvalidParms; else if (IconSize > 0) RtlCopyMemory((PBYTE)pIconInfo + sizeof(ICONINFO), pIconBitMap, IconSize); } AfpSwmrRelease(&AfpIconListLock); return AFP_ERR_NONE; }
/*** afpLookupIconInGlobalList
* * The global list of icons is a server maintained list updates by the service. * This is called by AfpLookupIcon() when the specified icon is not found in * the volume desktop. * * LOCKS: AfpIconListLock (SWMR, Shared); */ LOCAL AFPSTATUS afpLookupIconInGlobalList( IN DWORD Creator, IN DWORD Type, IN DWORD IconType, IN PLONG pSize, OUT PBYTE pBitMap ) { PICONINFO pIconInfo; AFPSTATUS Status = AFP_ERR_NONE;
PAGED_CODE( );
AfpSwmrAcquireShared(&AfpIconListLock); pIconInfo = AfpGlobalIconList; for (pIconInfo = AfpGlobalIconList; pIconInfo != NULL; pIconInfo = pIconInfo->icon_Next) { if ((pIconInfo->icon_Type == Type) && (pIconInfo->icon_Creator == Creator) && (pIconInfo->icon_IconType == (USHORT)IconType)) break; } if (pIconInfo == NULL) Status = AFP_ERR_ITEM_NOT_FOUND; else { if (*pSize > pIconInfo->icon_Size) *pSize = pIconInfo->icon_Size; if (*pSize > 0) RtlCopyMemory(pBitMap, (PBYTE)pIconInfo + sizeof(ICONINFO), *pSize); } AfpSwmrRelease(&AfpIconListLock); return Status; }
/*** AfpFreeGlobalIconList
* * Called at server stop time to free the memory allocated for the global * icons. * * LOCKS: AfpIconListLock (SWMR, Exclusive); */ VOID AfpFreeGlobalIconList( VOID ) { PICONINFO pIconInfo;
PAGED_CODE( );
AfpSwmrAcquireExclusive(&AfpIconListLock);
for (pIconInfo = AfpGlobalIconList; pIconInfo != NULL; ) { PICONINFO pFree;
pFree = pIconInfo; pIconInfo = pIconInfo->icon_Next; AfpFreeMemory (pFree); }
AfpSwmrRelease(&AfpIconListLock); }
/*** afpGetGlobalIconInfo
* * The global list of icons is a server maintained list updates by the service. * This is called by AfpLookupIconInfo() when the specified icon is not found * in the volume desktop. * * LOCKS: AfpIconListLock (SWMR, Shared) */ LOCAL AFPSTATUS afpGetGlobalIconInfo( IN DWORD Creator, OUT PDWORD pType, OUT PDWORD pIconType, OUT PDWORD pTag, OUT PLONG pSize ) { PICONINFO pIconInfo; AFPSTATUS Status = AFP_ERR_NONE;
PAGED_CODE( );
AfpSwmrAcquireExclusive(&AfpIconListLock); pIconInfo = AfpGlobalIconList; for (pIconInfo = AfpGlobalIconList; pIconInfo != NULL; pIconInfo = pIconInfo->icon_Next) { if (pIconInfo->icon_Creator == Creator) break; } if (pIconInfo == NULL) Status = AFP_ERR_ITEM_NOT_FOUND; else { *pType = pIconInfo->icon_Type; *pIconType = pIconInfo->icon_IconType; *pTag = pIconInfo->icon_Tag; *pSize = pIconInfo->icon_Size; } AfpSwmrRelease(&AfpIconListLock); return Status; }
/*** afpReadDesktopFromDisk
* * Read the desktop database from the desktop stream. No locks are required * for this routine since it only operates on volume descriptors which are * newly created and not yet linked into the global volume list. */ LOCAL NTSTATUS afpReadDesktopFromDisk( IN PVOLDESC pVolDesc, IN PFILESYSHANDLE pfshDesktop ) { DESKTOP Desktop; PAPPLINFO2 *ppApplInfo; PICONINFO *ppIconInfo; NTSTATUS Status; DWORD DskOffst; FORKOFFST ForkOffset; PBYTE pBuffer; LONG i, SizeRead, BufOffst = 0; LONG PrevHash, applSize;
PAGED_CODE( );
DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_INFO, ("\tReading Desktop from disk....\n") );
// Work with one page of memory and do multiple I/Os to the disk.
if ((pBuffer = AfpAllocNonPagedMemory(DESKTOPIO_BUFSIZE)) == NULL) { return STATUS_INSUFFICIENT_RESOURCES; }
ForkOffset.QuadPart = DskOffst = 0;
// Read in the desktop header and validate it
Status = AfpIoRead(pfshDesktop, &ForkOffset, sizeof(DESKTOP), &SizeRead, (PBYTE)&Desktop);
if (!NT_SUCCESS(Status) ||
(SizeRead != sizeof(DESKTOP)) ||
(Desktop.dtp_Signature != AFP_SERVER_SIGNATURE) ||
((Desktop.dtp_Version != AFP_DESKTOP_VERSION1) && (Desktop.dtp_Version != AFP_DESKTOP_VERSION2)) ||
((Desktop.dtp_cApplEnts > 0) && ((ULONG_PTR)(Desktop.dtp_pApplInfo) != sizeof(DESKTOP))) ||
((Desktop.dtp_cIconEnts > 0) && ((ULONG_PTR)(Desktop.dtp_pIconInfo) != sizeof(DESKTOP) + (Desktop.dtp_cApplEnts * ((Desktop.dtp_Version == AFP_DESKTOP_VERSION1) ? sizeof(APPLINFO) : sizeof(APPLINFO2))) )) ) { AFPLOG_ERROR(AFPSRVMSG_READ_DESKTOP, Status, NULL, 0, &pVolDesc->vds_Name); goto desktop_corrupt; }
switch (Desktop.dtp_Version) { case AFP_DESKTOP_VERSION1: { AFPLOG_INFO(AFPSRVMSG_UPDATE_DESKTOP_VERSION, STATUS_SUCCESS, NULL, 0, &pVolDesc->vds_Name);
applSize = sizeof(APPLINFO);
break; } case AFP_DESKTOP_VERSION2: { applSize = sizeof(APPLINFO2); break; } default: { // This should never happen since it was checked above
DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_WARN, ("afpReadDesktopFromDisk: Unexpected DT version 0x%lx\n", Desktop.dtp_Version) ); ASSERTMSG("afpReadDesktopFromDisk: Unexpected DT Version", 0); goto desktop_corrupt; } }
// Initialize the desktop header. Even though we may be reading a
// downlevel version database, set the in-memory desktop database
// version to current version since we are building it with the
// current appl version structure.
AfpDtHdrToVolDesc(&Desktop, pVolDesc);
ForkOffset.QuadPart = DskOffst = sizeof(DESKTOP); SizeRead = 0;
// Now read in the APPL entries, if any
for (i = 0, PrevHash = -1; (Status == AFP_ERR_NONE) && (i < Desktop.dtp_cApplEnts); i++) { PAPPLINFO2 pApplInfo;
if ((SizeRead - BufOffst) < applSize) { // We have a partial APPLINFO. Backup and read the whole thing
DskOffst -= ((DWORD)SizeRead - (DWORD)BufOffst); ForkOffset.QuadPart = DskOffst; Status = AfpIoRead(pfshDesktop, &ForkOffset, DESKTOPIO_BUFSIZE, &SizeRead, pBuffer); if ((Status != AFP_ERR_NONE) || (SizeRead < applSize)) { AFPLOG_ERROR(AFPSRVMSG_READ_DESKTOP, Status, &SizeRead, sizeof(SizeRead), &pVolDesc->vds_Name); Status = STATUS_UNEXPECTED_IO_ERROR; break; } DskOffst += SizeRead; ForkOffset.QuadPart = DskOffst; BufOffst = 0; }
if ((pApplInfo = ALLOC_APPLINFO()) == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; AFPLOG_ERROR(AFPSRVMSG_READ_DESKTOP, Status, NULL, 0, &pVolDesc->vds_Name); break; } pApplInfo->appl_ParentID = 0; // If we are reading downlevel appl structures, they will
// get read into the first part of the current appl structures.
// These fields should be identical! If this is the case, the
// appl_ParentId field will be 0 and the volume marked as needing
// its appls rebuilt.
RtlCopyMemory(pApplInfo, pBuffer + BufOffst, applSize); pApplInfo->appl_Next = NULL; BufOffst += applSize; if (PrevHash != (LONG)HASH_APPL(pApplInfo->appl_Creator)) { PrevHash = (LONG)HASH_APPL(pApplInfo->appl_Creator); ppApplInfo = &pVolDesc->vds_pApplBuckets[PrevHash]; } *ppApplInfo = pApplInfo; ppApplInfo = &pApplInfo->appl_Next; }
// Now read in the ICON entries, if any
for (i = 0, PrevHash = -1; (Status == AFP_ERR_NONE) && (i < Desktop.dtp_cIconEnts); i++) { PICONINFO pIconInfo;
if ((SizeRead - BufOffst) < sizeof(ICONINFO)) { // We have a partial ICONINFO. Backup and read the whole thing
DskOffst -= ((DWORD)SizeRead - (DWORD)BufOffst); ForkOffset.QuadPart = DskOffst; Status = AfpIoRead(pfshDesktop, &ForkOffset, DESKTOPIO_BUFSIZE, &SizeRead, pBuffer); if ((Status != AFP_ERR_NONE) || (SizeRead < sizeof(ICONINFO))) { AFPLOG_ERROR(AFPSRVMSG_READ_DESKTOP, Status, &SizeRead, sizeof(SizeRead), &pVolDesc->vds_Name); Status = STATUS_UNEXPECTED_IO_ERROR; break; } DskOffst += SizeRead; ForkOffset.QuadPart = DskOffst; BufOffst = 0; }
// Validate icon size
if ((((PICONINFO)(pBuffer + BufOffst))->icon_Size > ICONSIZE_ICN8) || (((PICONINFO)(pBuffer + BufOffst))->icon_Size < ICONSIZE_ICS)) { Status = STATUS_UNEXPECTED_IO_ERROR; AFPLOG_ERROR(AFPSRVMSG_READ_DESKTOP, Status, &((PICONINFO)(pBuffer + BufOffst))->icon_Size, sizeof(((PICONINFO)(0))->icon_Size), &pVolDesc->vds_Name); break; }
if ((pIconInfo = ALLOC_ICONINFO(((PICONINFO)(pBuffer + BufOffst))->icon_Size)) == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; AFPLOG_ERROR(AFPSRVMSG_READ_DESKTOP, Status, NULL, 0, &pVolDesc->vds_Name); break; }
// First copy the icon header and then link the icon into the hash table
RtlCopyMemory(pIconInfo, pBuffer + BufOffst, sizeof(ICONINFO));
pIconInfo->icon_Next = NULL; if (PrevHash != (LONG)HASH_ICON(pIconInfo->icon_Creator)) { PrevHash = (LONG)HASH_ICON(pIconInfo->icon_Creator); ppIconInfo = &pVolDesc->vds_pIconBuckets[PrevHash]; } *ppIconInfo = pIconInfo; ppIconInfo = &pIconInfo->icon_Next;
// Now check if there is sufficient stuff here to get the icon
BufOffst += sizeof(ICONINFO); if ((SizeRead - BufOffst) < pIconInfo->icon_Size) { LONG Size2Copy;
Size2Copy = SizeRead - BufOffst;
// Copy what we can first
RtlCopyMemory((PBYTE)pIconInfo + sizeof(ICONINFO), pBuffer + BufOffst, Size2Copy);
Status = AfpIoRead(pfshDesktop, &ForkOffset, DESKTOPIO_BUFSIZE, &SizeRead, pBuffer); if ((Status != AFP_ERR_NONE) || (SizeRead < (pIconInfo->icon_Size - Size2Copy))) { AFPLOG_ERROR(AFPSRVMSG_READ_DESKTOP, Status, &SizeRead, sizeof(SizeRead), &pVolDesc->vds_Name); Status = STATUS_UNEXPECTED_IO_ERROR; break; } DskOffst += SizeRead; ForkOffset.QuadPart = DskOffst;
// Now copy the rest of the icon
RtlCopyMemory((PBYTE)pIconInfo + sizeof(ICONINFO) + Size2Copy, pBuffer, pIconInfo->icon_Size - Size2Copy);
BufOffst = pIconInfo->icon_Size - Size2Copy; } else { RtlCopyMemory((PBYTE)pIconInfo + sizeof(ICONINFO), pBuffer + BufOffst, pIconInfo->icon_Size);
BufOffst += pIconInfo->icon_Size; } }
if (Status != AFP_ERR_NONE) { AfpFreeDesktopTables(pVolDesc); desktop_corrupt: // We have essentially ignored the existing data in the stream
// Initialize the header
pVolDesc->vds_cApplEnts = 0; pVolDesc->vds_cIconEnts = 0;
AfpVolDescToDtHdr(pVolDesc, &Desktop); Desktop.dtp_pIconInfo = NULL; Desktop.dtp_pApplInfo = NULL; AfpIoWrite(pfshDesktop, &LIZero, sizeof(DESKTOP), (PBYTE)&Desktop);
// Truncate the stream at this point
AfpIoSetSize(pfshDesktop, sizeof(DESKTOP)); Status = STATUS_SUCCESS; }
if (pBuffer != NULL) AfpFreeMemory(pBuffer);
return Status; }
/*** AfpInitDesktop
* * This routine initializes the memory image (and all related volume * descriptor fields) of the desktop for a newly added volume. If a desktop * stream already exists on the disk for the volume root directory, that * stream is read in. If this is a newly created volume, the desktop * stream is created on the root of the volume. If this is a CD-ROM volume, * only the memory image is initialized. * * No locks are necessary since this routine only operates on volume * descriptors which are newly allocated, but not yet linked into the global * volume list. */ AFPSTATUS AfpInitDesktop( IN PVOLDESC pVolDesc, OUT BOOLEAN *pfNewVolume ) { BOOLEAN InitHeader = True; NTSTATUS Status = STATUS_SUCCESS; FILESYSHANDLE fshDesktop;
PAGED_CODE( );
// for now
*pfNewVolume = FALSE;
DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_INFO, ("\tInitializing Desktop...\n") ); AfpSwmrInitSwmr(&(pVolDesc->vds_DtAccessLock));
// if this is an NTFS volume, attempt to create the desktop stream.
// If it already exists, open it and read it in.
if (IS_VOLUME_NTFS(pVolDesc)) { ULONG CreateInfo;
InitHeader = False;
Status = AfpIoCreate(&pVolDesc->vds_hRootDir, AFP_STREAM_DT, &UNullString, FILEIO_ACCESS_READWRITE, FILEIO_DENY_WRITE, FILEIO_OPEN_FILE, FILEIO_CREATE_INTERNAL, FILE_ATTRIBUTE_NORMAL, False, NULL, &fshDesktop, &CreateInfo, NULL, NULL, NULL);
if (NT_SUCCESS(Status)) { if (CreateInfo == FILE_OPENED) { Status = afpReadDesktopFromDisk(pVolDesc, &fshDesktop); AfpIoClose(&fshDesktop); } else if (CreateInfo != FILE_CREATED) { DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_ERR, ("AfpInitDesktop: Unexpected create action 0x%lx\n", CreateInfo) ); ASSERT(0); // this should never happen
Status = STATUS_UNSUCCESSFUL; } else { DBGPRINT(DBG_COMP_VOLUME, DBG_LEVEL_ERR, ("AfpInitDesktop: volume %Z is new\n",&pVolDesc->vds_Name));
InitHeader = True; *pfNewVolume = TRUE; } } else { DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_ERR, ("AfpInitDesktop: AfpIoCreate failed %lx\n", Status)); Status = STATUS_UNSUCCESSFUL; } }
if (InitHeader) { DESKTOP Desktop;
// Initialize the header
pVolDesc->vds_cApplEnts = 0; pVolDesc->vds_cIconEnts = 0;
if (IS_VOLUME_NTFS(pVolDesc)) { AfpVolDescToDtHdr(pVolDesc, &Desktop); Desktop.dtp_pIconInfo = NULL; Desktop.dtp_pApplInfo = NULL; AfpIoWrite(&fshDesktop, &LIZero, sizeof(DESKTOP), (PBYTE)&Desktop); AfpIoClose(&fshDesktop); } } return Status; }
/*** AfpUpdateDesktop
* * Update the desktop database on the volume root. The swmr access is held * for read (by the caller) while the update is in progress. It is already * determined by the caller that the volume desktop needs to be updated. * * LOCKS: vds_DtAccessLock (SWMR, Shared) */ VOID AfpUpdateDesktop( IN PVOLDESC pVolDesc // Volume Descriptor of the open volume
) { AFPSTATUS Status; PBYTE pBuffer; DWORD Offset = 0, Size; LONG i; DESKTOP Desktop; FORKOFFST ForkOffset; FILESYSHANDLE fshDesktop; ULONG CreateInfo; #ifdef PROFILING
TIME TimeS, TimeE, TimeD;
PAGED_CODE( );
INTERLOCKED_INCREMENT_LONG(&AfpServerProfile->perf_DesktopUpdCount); AfpGetPerfCounter(&TimeS); #endif
// Take the swmr so that nobody can initiate changes to the desktop
AfpSwmrAcquireShared(&pVolDesc->vds_DtAccessLock);
DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_INFO, ("\tWriting Desktop to disk....\n") );
do { fshDesktop.fsh_FileHandle = NULL; // Work with one page of memory and do multiple I/Os to the disk.
if ((pBuffer = AfpAllocPagedMemory(DESKTOPIO_BUFSIZE)) == NULL) { AFPLOG_ERROR(AFPSRVMSG_WRITE_DESKTOP, STATUS_NO_MEMORY, NULL, 0, &pVolDesc->vds_Name); break; }
// Open a handle to the desktop stream, denying others read/write
// access (i.e. backup/restore)
Status = AfpIoCreate(&pVolDesc->vds_hRootDir, AFP_STREAM_DT, &UNullString, FILEIO_ACCESS_WRITE, FILEIO_DENY_ALL, FILEIO_OPEN_FILE, FILEIO_CREATE_INTERNAL, FILE_ATTRIBUTE_NORMAL, False, NULL, &fshDesktop, &CreateInfo, NULL, NULL, NULL);
if (NT_SUCCESS(Status)) { if ((CreateInfo != FILE_OPENED) && (CreateInfo != FILE_CREATED)) { // This should never happen!
DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_WARN, ("AfpUpdateDesktop: Unexpected create action 0x%lx\n", CreateInfo) ); ASSERTMSG("AfpUpdateDesktop: Unexpected create action", 0); break; } } else { AFPLOG_ERROR(AFPSRVMSG_WRITE_DESKTOP, Status, NULL, 0, &pVolDesc->vds_Name); break; }
// Snapshot the header and write it with an invalid signature. We write
// the header again later with a valid signature. This protects us from
// incomplete writes (server crash etc.)
AfpVolDescToDtHdr(pVolDesc, &Desktop); Desktop.dtp_Signature = 0;
(ULONG_PTR)(Desktop.dtp_pApplInfo) = 0; if (Desktop.dtp_cApplEnts > 0) (ULONG_PTR)(Desktop.dtp_pApplInfo) = sizeof(DESKTOP);
(ULONG_PTR)(Desktop.dtp_pIconInfo) = 0; if (Desktop.dtp_cIconEnts > 0) (ULONG_PTR)(Desktop.dtp_pIconInfo) = sizeof(DESKTOP) + sizeof(APPLINFO2)*Desktop.dtp_cApplEnts;
// Write out the header with invalid signature
Status = AfpIoWrite(&fshDesktop, &LIZero, sizeof(DESKTOP), (PBYTE)&Desktop);
Offset = sizeof(DESKTOP); Size = 0;
// First write the APPL Entries
for (i = 0; (Status == AFP_ERR_NONE) && (i < APPL_BUCKETS); i++) { PAPPLINFO2 pApplInfo;
for (pApplInfo = pVolDesc->vds_pApplBuckets[i]; (Status == AFP_ERR_NONE) && (pApplInfo != NULL); pApplInfo = pApplInfo->appl_Next) { if ((DESKTOPIO_BUFSIZE - Size) < sizeof(APPLINFO2)) { DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_INFO, ("afpUpdateDesktop: Writing Appl %ld at %ld\n", Size, Offset));
ForkOffset.QuadPart = Offset; Status = AfpIoWrite(&fshDesktop, &ForkOffset, Size, pBuffer); Size = 0; Offset += Size; } *(PAPPLINFO2)(pBuffer + Size) = *pApplInfo; Size += sizeof(APPLINFO2); } }
// And now the ICON entries
for (i = 0; (Status == AFP_ERR_NONE) && (i < ICON_BUCKETS); i++) { PICONINFO pIconInfo;
for (pIconInfo = pVolDesc->vds_pIconBuckets[i]; (Status == AFP_ERR_NONE) && (pIconInfo != NULL); pIconInfo = pIconInfo->icon_Next) { if ((DESKTOPIO_BUFSIZE - Size) < (sizeof(ICONINFO) + pIconInfo->icon_Size)) { DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_INFO, ("afpUpdateDesktop: Writing icons %ld at %ld\n", Size, Offset));
ForkOffset.QuadPart = Offset; Status = AfpIoWrite(&fshDesktop, &ForkOffset, Size, pBuffer); Offset += Size; Size = 0; } RtlCopyMemory(pBuffer + Size, (PBYTE)pIconInfo, sizeof(ICONINFO) + pIconInfo->icon_Size); Size += sizeof(ICONINFO) + pIconInfo->icon_Size; } }
while (Status == AFP_ERR_NONE) { if (Size > 0) { DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_INFO, ("afpUpdateDesktop: Writing at end %ld at %ld\n", Size, Offset));
ForkOffset.QuadPart = Offset; Status = AfpIoWrite(&fshDesktop, &ForkOffset, Size, pBuffer); if (Status != AFP_ERR_NONE) break; }
DBGPRINT(DBG_COMP_DESKTOP, DBG_LEVEL_INFO, ("afpUpdateDesktop: Setting desktop stream size @ %ld\n", Size + Offset)); // Chop off the stream at this offset.
Status = AfpIoSetSize(&fshDesktop, Offset + Size);
ASSERT (Status == AFP_ERR_NONE);
// Write the correct signature back
Desktop.dtp_Signature = AFP_SERVER_SIGNATURE;
Status = AfpIoWrite(&fshDesktop, &LIZero, sizeof(DESKTOP), (PBYTE)&Desktop);
// Update the count of changes: vds_cChangesDt is protected by the
// swmr, the scavenger can set this with READ access. All others
// MUST hold the swmr for WRITE access to increment the cChangesDt.
// Scavenger is the only consumer of vds_cScvgrDt, so no lock is
// really needed for it.
pVolDesc->vds_cScvgrDt = 0; break; }
if (Status != AFP_ERR_NONE) { AFPLOG_ERROR(AFPSRVMSG_WRITE_DESKTOP, Status, NULL, 0, &pVolDesc->vds_Name); }
} while (False);
if (pBuffer != NULL) { AfpFreeMemory(pBuffer); if (fshDesktop.fsh_FileHandle != NULL) AfpIoClose(&fshDesktop); } #ifdef PROFILING
AfpGetPerfCounter(&TimeE); TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart; INTERLOCKED_ADD_LARGE_INTGR(&AfpServerProfile->perf_DesktopUpdTime, TimeD, &AfpStatisticsLock); #endif
AfpSwmrRelease(&pVolDesc->vds_DtAccessLock); }
/*** AfpFreeDesktopTables
* * Free the allocated memory for the volume desktop tables. The volume is * about to be deleted. Ensure that either the volume is non-NTFS or it is * clean i.e. the scavenger threads have written it back. No locks are needed * as this structure is all by itself. */ VOID AfpFreeDesktopTables( IN PVOLDESC pVolDesc ) { LONG i;
PAGED_CODE( );
// This should never happen
ASSERT (!IS_VOLUME_NTFS(pVolDesc) || (pVolDesc->vds_pOpenForkDesc == NULL));
// First tackle the ICON list. Traverse each of the hash indices.
// Note that the icon is allocated as part of the IconInfo structure
// so free in together.
for (i = 0; i < ICON_BUCKETS; i++) { PICONINFO pIconInfo, pFree;
for (pIconInfo = pVolDesc->vds_pIconBuckets[i]; pIconInfo != NULL; ) { pFree = pIconInfo; pIconInfo = pIconInfo->icon_Next; AfpFreeMemory(pFree); } // In case we ever try to free the table again
pVolDesc->vds_pIconBuckets[i] = NULL; }
// Now tackle the APPL list. Traverse each of the hash indices.
for (i = 0; i < APPL_BUCKETS; i++) { PAPPLINFO2 pApplInfo, pFree;
for (pApplInfo = pVolDesc->vds_pApplBuckets[i]; pApplInfo != NULL; ) { pFree = pApplInfo; pApplInfo = pApplInfo->appl_Next; AfpFreeMemory(pFree); } // In case we ever try to free the table again
pVolDesc->vds_pApplBuckets[i] = NULL; } }
|