windows-nt/Source/XPSP1/NT/shell/osshell/dskquota/control/sidcache.cpp
2020-09-26 16:20:57 +08:00

4281 lines
134 KiB
C++

///////////////////////////////////////////////////////////////////////////////
/* File: sidcache.cpp
Description: This module provides the functionality for a cache of user
SID/Name pairs. See the file header in sidcache.h for details.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
07/12/96 Initial creation. BrianAu
08/14/96 Added SidCacheQueueIterator. BrianAu
09/20/96 Total redesign. Old design loaded data from file BrianAu
into an in-memory hash table. New design leaves
everything on disk and merely maps the file into
memory. Much more efficient with respect to
speed and size.
07/02/97 Added SidNameCache::GetFileNames. Brianau
Changed logic for identifying cache file path.
Removed index bucket count param from registry.
03/18/98 Replaced "domain", "name" and "full name" with BrianAu
"container", "logon name" and "display name" to
better match the actual contents. This was in
reponse to making the quota UI DS-aware. The
"logon name" is now a unique key as it contains
both account name and domain-like information.
i.e. "REDMOND\brianau" or "brianau@microsoft.com".
*/
///////////////////////////////////////////////////////////////////////////////
#include "pch.h" // PCH
#pragma hdrstop
#include "dskquota.h"
#include "sidcache.h"
#include "registry.h"
//
// Verify that build is UNICODE.
//
#if !defined(UNICODE)
# error This module must be compiled UNICODE.
#endif
//
// How long (milliseconds) we'll wait to get a lock on the cache.
//
const DWORD MUTEX_WAIT_TIMEOUT = 5000;
//
// Byte value used to fill in unused blocks in the data file.
//
const BYTE RECORD_UNUSED_BYTE = 0xCC;
//
// A value to signify the start of a record in the data file.
// The bit pattern is 1010101010101010 0101010101010101
// Highly unlikely that any data file data will produce this
// pattern.
//
const DWORD RECORD_SIGNATURE = 0xAAAA5555;
//
// Signatures written into the header of the index and data files.
// For validating a file just in case someone's put another file
// in their place. The numbers are arbitrary.
// 2600 means "MS building 26N (where I'm working now).
// 3209 is my office number.
// 3210 is BobDay's office number (across the hall).
// Hey, I had to pick something.
//
const DWORD INDEX_FILE_SIGNATURE = 0x26003209;
const DWORD DATA_FILE_SIGNATURE = 0x26003210;
//
// A version number so a future build of the software won't be confused
// by a change in file formats. Bump this if the file format changes.
//
const DWORD FILE_VERSION = 0x00000003;
//
// Average number of 32-byte blocks per cache entry.
// Entries are variable length (SID, Name etc). This average is used
// in initially sizing the data file. I've found that most entries (by far)
// require 4 blocks. Both the data file and index file grow independently
// as needed so it isn't a problem if this isn't always accurate.
//
const DWORD AVG_BLOCKS_PER_ENTRY = 4;
//
// Create space for this many records in a new data file.
// Since the data and index files grow automatically as needed,
// this can change as you see fit.
//
#if DBG
const DWORD NEW_CACHE_ENTRY_COUNT = 4; // Force frequent file growth.
#else
const DWORD NEW_CACHE_ENTRY_COUNT = 128;
#endif
//
// The index and data files automatically grow when needed. These
// values control how much they grow by.
//
#if DBG
const DWORD DATA_FILE_GROW_BLOCKS = 4; // Force frequent file growth.
#else
const DWORD DATA_FILE_GROW_BLOCKS = 512;
#endif
const DWORD INDEX_FILE_GROW_ENTRIES = (DATA_FILE_GROW_BLOCKS / AVG_BLOCKS_PER_ENTRY);
//
// The number of buckets in the cache index hash table.
// Number should be prime. Remember, this index is on disk so we can afford to
// have a reasonably large hash table. While it would be nice to fit the
// buckets within a single page of memory, that would be too small to be effective
// 512 / 4 == 64 buckets. There's also no guarantee that all buckets would be
// mapped to a single physical page.
//
const DWORD INDEX_BUCKET_COUNT = 503;
//
// Convert between blocks and bytes.
// BLOCK_SIZE is a power of 2 so the multiply and division
// can be optimized to shifts.
//
#define BYTE_TO_BLOCK(b) ((b) / BLOCK_SIZE)
#define BLOCK_TO_BYTE(b) ((b) * BLOCK_SIZE)
//
// Base pointers for mapped data and index files.
// When the files are mapped into memory, these globals contain
// the address of the mapped memory.
//
LPBYTE g_pbMappedDataFile;
LPBYTE g_pbMappedIndexFile;
//
// Macros for working with based pointers.
// Pointer members in the file structures contain offsets relative
// to the start of the file. When dereferencing these pointers,
// they must be converted to "based" pointers which add the file's
// base address to the pointer value. This results in a true
// virtual address that can be accessed.
//
#if defined(_X86_)
# define NDX_BASED(t) t __based(g_pbMappedIndexFile)
# define DAT_BASED(t) t __based(g_pbMappedDataFile)
# define NDX_BASED_CAST(t,e) (NDX_BASED(t) *)((DWORD)(e))
# define DAT_BASED_CAST(t,e) (DAT_BASED(t) *)((DWORD)(e))
#else
//
// APPCOMPAT:
// I think there's a bug in the ALPHA compiler that is preventing __based pointers
// from working as I have used them.
// This is a workaround until the bug is fixed or I find out what I'm doing wrong.
//
# define NDX_BASED(t) t
# define DAT_BASED(t) t
# define NDX_BASED_CAST(t,e) ((NDX_BASED(t) *)(((BYTE *)g_pbMappedIndexFile) + ((DWORD_PTR)(e))))
# define DAT_BASED_CAST(t,e) ((DAT_BASED(t) *)(((BYTE *)g_pbMappedDataFile) + ((DWORD_PTR)(e))))
#endif
//
// Macros to verify that the files have been mapped.
// These are primarily used in assertions.
//
#define INDEX_FILE_MAPPED (NULL != g_pbMappedIndexFile)
#define DATA_FILE_MAPPED (NULL != g_pbMappedDataFile)
//
// Names for system objects. Mutex and maps are named so they can
// be shared between processes.
//
const LPCTSTR g_szSidCacheMutex = TEXT("DSKQUOTA_SIDCACHE_MUTEX");
const LPCTSTR g_pszIndexFileMapping = TEXT("DSKQUOTA_SIDCACHE_INDEX");
const LPCTSTR g_pszDataFileMapping = TEXT("DSKQUOTA_SIDCACHE_DATA");
//
// Use to clear a file's GUID and to test for a NULL guid.
//
static const GUID GUID_Null =
{ 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };
//
// Registry parameter value names.
//
const TCHAR g_szSidCacheRecLifeMin[] = TEXT("SidCacheRecLifeMin");
const TCHAR g_szSidCacheRecLifeMax[] = TEXT("SidCacheRecLifeMax");
//***********************************************************************************
//***********************************************************************************
// C A C H E M A N A G E R
//***********************************************************************************
//***********************************************************************************
//
// Called by both SidNameCache_Get and SidNameCache_Destroy to either retrieve
// the address (and create if necessary) of the singleton cache object or to
// destroy that object. The reason to have this single function is so that
// the singleton pointer is a local variable, inaccessible outside of this
// function. This way the only access to the cache object is through
// the SidNameCache_Get function.
//
HRESULT
SidNameCache_GetOrDestroy(
SidNameCache **ppCache,
bool bGet
)
{
DBGASSERT((NULL != ppCache));
HRESULT hr = E_FAIL;
//
// This is the one-and-only pointer to the SID-Name cache object.
// All code obtains this address through this function.
//
static SidNameCache *pTheCache;
*ppCache = NULL;
HANDLE hMutex = CreateMutex(NULL, FALSE, g_szSidCacheMutex);
if (NULL != hMutex)
{
if (WAIT_OBJECT_0 == WaitForSingleObject(hMutex, INFINITE))
{
if (!bGet)
{
//
// Destroy the existing cache object. NULL the static
// ptr so next request will recreate the cache object.
//
delete pTheCache;
pTheCache = NULL;
hr = S_OK;
}
else
{
//
// Retrieve existing or create new cache object.
//
SidNameCache *pCache = pTheCache;
if (NULL != pCache)
{
//
// Use the existing object.
//
hr = S_OK;
}
else
{
try
{
//
// Doesn't yet exist. Create new one.
//
autoptr<SidNameCache> ptrCache(new SidNameCache);
//
// Open/Create new cache data and index files.
// Will first try to open existing. If either the index or
// data file doesn't exist or is invalid, new files are created.
//
hr = ptrCache->Initialize(TRUE);
if (SUCCEEDED(hr))
{
pCache = ptrCache.get();
ptrCache.disown();
//
// Save in our static variable for future use.
//
pTheCache = pCache;
}
else
{
DBGERROR((TEXT("SID cache initialization failed with error 0x%08X"), hr));
}
}
catch(CAllocException& e)
{
hr = E_OUTOFMEMORY;
}
catch(CSyncException& e)
{
hr = E_FAIL;
}
}
if (SUCCEEDED(hr))
{
//
// Return the pointer to the caller.
//
*ppCache = pCache;
}
}
ReleaseMutex(hMutex);
}
CloseHandle(hMutex);
}
return hr;
}
//
// Retrieves the address of the singleton SidNameCache object.
// If the object doesn't exist it is created.
// Callers do not release or delete this pointer.
// On Process-Detach, the SidNameCache_Destroy() function is
// called to delete the singleton object.
//
HRESULT SidNameCache_Get(SidNameCache **ppCache)
{
const bool bGet = true;
return SidNameCache_GetOrDestroy(ppCache, bGet);
}
//
// Called on Process-Detach to destroy the singleton cache
// object.
//
HRESULT SidNameCache_Destroy(void)
{
const bool bGet = false;
SidNameCache *pUnused;
return SidNameCache_GetOrDestroy(&pUnused, bGet);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::SidNameCache
Description: Constructor. Creates an empty SID/Name cache object.
Call one of the Initialize() methods to either create a new index and
data file or to open existing ones.
Arguments: None.
Returns: Nothing.
Exceptions: SyncObjErrorCreate.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
SidNameCache::SidNameCache(
VOID
) : m_hMutex(NULL),
m_pIndexMgr(NULL),
m_pRecordMgr(NULL)
{
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::SidNameCache")));
if (NULL == (m_hMutex = CreateMutex(NULL, // No security
FALSE, // Non-owned
g_szSidCacheMutex)))
{
throw CSyncException(CSyncException::mutex, CSyncException::create);
}
SetCacheFilePath();
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::~SidNameCache
Description: Destructor. Destroys the cache object by deleting the
Index Manager and Record Manager objects. The respective destructor's
for each of the managers will handle closing their files and mapping
objects.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
SidNameCache::~SidNameCache(
VOID
)
{
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::~SidNameCache")));
if (NULL != m_hMutex)
Lock();
delete m_pIndexMgr;
delete m_pRecordMgr;
if (NULL != m_hMutex)
{
ReleaseLock();
CloseHandle(m_hMutex);
}
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::Initialize
Description: Initializes a new cache object by creating the Index and
Record manager objects then initializing each. Initialization first
tries to open existing index and data files. If one (or both) of the
files does not exist OR one (or both) of the files is considered
"invalid", new files are created. Need to take a "fail safe" approach
to this.
Arguments:
bOpenExisting - TRUE = Try to open an existing cache index and data file.
If it can't, it creates a new one. FALSE = Just create a new one.
Returns:
NO_ERROR - Success.
E_FAIL - Could not open nor create required files.
Exceptions: OutOfMemory.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
HRESULT
SidNameCache::Initialize(
BOOL bOpenExisting
)
{
DBGTRACE((DM_CONTROL, DL_MID, TEXT("SidNameCache::Initialize")));
DBGASSERT((NULL == m_pIndexMgr));
DBGASSERT((NULL == m_pRecordMgr));
HRESULT hResult = E_FAIL;
CacheAutoLock lock(*this);
if (lock.Lock())
{
try
{
if (m_strFilePath.IsEmpty())
{
//
// If file path is empty, it means we couldn't get the
// user's profile directory from the registry.
//
DBGERROR((TEXT("Error creating SID cache files. No path.")));
}
else
{
//
// Name for our cache data and index files.
// Will append .DAT and .NDX respectively.
// This is where you change the file name or extension(s)
// if you want to do that.
//
const TCHAR szSidCacheFile[] = TEXT("NTDiskQuotaSidCache");
//
// Create a fully-qualified path for the cache data and index
// files. m_strFilePath was set in the cache object ctor.
//
CString strDataFile(m_strFilePath);
CString strIndexFile(m_strFilePath);
strDataFile.Format(TEXT("%1\\%2.dat"), (LPCTSTR)m_strFilePath, szSidCacheFile);
strIndexFile.Format(TEXT("%1\\%2.ndx"),(LPCTSTR)m_strFilePath, szSidCacheFile);
//
// Create the record and index manager objects.
//
m_pRecordMgr = new RecordMgr(*this);
m_pIndexMgr = new IndexMgr(*this);
DBGPRINT((DM_CONTROL, DL_MID, TEXT("Create SID cache DataFile = %s IndexFile = %s"),
(LPCTSTR)strDataFile, (LPCTSTR)strIndexFile));
if (bOpenExisting)
{
//
// First try opening existing data and index files.
//
if (NULL != m_pRecordMgr->Initialize(strDataFile))
{
if (NULL != m_pIndexMgr->Initialize(strIndexFile))
hResult = NO_ERROR;
}
}
if (FAILED(hResult) || !FilesAreValid())
{
hResult = E_FAIL;
//
// Couldn't open existing files, try creating new ones.
// Any open files/mappings will be closed.
//
if (NULL != m_pRecordMgr->Initialize(strDataFile,
NEW_CACHE_ENTRY_COUNT * AVG_BLOCKS_PER_ENTRY))
{
if (NULL != m_pIndexMgr->Initialize(strIndexFile,
INDEX_BUCKET_COUNT,
NEW_CACHE_ENTRY_COUNT))
{
hResult = NO_ERROR;
}
}
}
}
}
catch(CAllocException& e)
{
delete m_pRecordMgr;
m_pRecordMgr = NULL;
delete m_pIndexMgr;
m_pIndexMgr = NULL;
hResult = E_OUTOFMEMORY;
}
//
// Mark files as "valid".
//
if (SUCCEEDED(hResult))
ValidateFiles();
}
return hResult;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::OpenMappedFile
Description: Opens or creates a file, maps the file into memory and
returns the address of the mapping.
Arguments:
pszFile - Address of name of file to create/open.
pszMapping - Name to give the mapping object. This object is
named so that if multiple processes map the same file (using
the same mapping name), the file is only mapped once.
dwCreation - Creation flag (CREATE_ALWAYS, OPEN_EXISTING);
cbFileHigh/Low - If creating a new file or extending an existing
these two arguments contain the desired size in bytes.
phFile - Address of handle variable to receive the open file's
handle value. Call CloseHandle on this to close the file.
phFileMapping - Address of handle variable to receive the open
file mapping's handle value. Call CloseFileMapping on this
to close the mapping.
Returns:
Address of the mapped file in memory. NULL on failure.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
07/21/97 Files are now created in user's profile under a BrianAu
"DiskQuota" subdirectory.
*/
///////////////////////////////////////////////////////////////////////////////
LPBYTE
SidNameCache::OpenMappedFile(
LPCTSTR pszFile,
LPCTSTR pszMapping,
DWORD dwCreation,
DWORD cbFileHigh,
DWORD cbFileLow,
PHANDLE phFile,
PHANDLE phFileMapping
)
{
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::OpenMappedFile")));
LPBYTE pbBase = NULL;
DBGASSERT((NULL != pszFile));
DBGASSERT((NULL != pszMapping));
DBGASSERT((NULL != phFile));
DBGASSERT((NULL != phFileMapping));
*phFile = CreateFile(pszFile,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
dwCreation,
FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
NULL);
if (INVALID_HANDLE_VALUE != *phFile)
{
if ((*phFileMapping = CreateFileMapping(*phFile,
NULL,
PAGE_READWRITE,
cbFileHigh,
cbFileLow,
pszMapping)) != NULL)
{
pbBase = (LPBYTE)MapViewOfFile(*phFileMapping,
FILE_MAP_WRITE,
0,
0,
0);
if (NULL == pbBase)
DBGERROR((TEXT("SIDCACHE - Failed to map view of file %s"),
pszFile));
}
else
{
DBGERROR((TEXT("SIDCACHE - Failed to create mapping %s for file %s"),
pszMapping, pszFile));
}
if (NULL == pbBase)
{
CloseHandle(*phFile);
*phFile = NULL;
}
}
else
DBGERROR((TEXT("SIDCACHE - Failed to open file %s"), pszFile));
return pbBase;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::SetCacheFilePath
Description: Obtains the fully-qualified path for the cache's data
and index files and stores the value in m_strFilePath. The files
are to be created under in the user's profile under the directory
\AppData\Microsoft\Windows NT\DiskQuota. We have to read the registry
to find exactly where this subtree lives for this user.
Arguments: None.
Returns: Nothing.
On return, m_strFilePath contains the path to the files.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
07/02/97 Initial creation. BrianAu
07/21/97 Removed default file path. BrianAu
Files can only be stored in user's profile.
Can't allow unsecure access to SID/Name pairs.
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::SetCacheFilePath(
VOID
)
{
DBGTRACE((DM_SIDCACHE, DL_HIGH, TEXT("SidNameCache::SetCacheFilePath")));
//
// Get the user's %UserProfile%\Application Data directory.
// Normally, an app gets this through SHGetSpecialFolderLocation or
// SHGetSpecialFolderPath. However, I don't want to load shell32.dll
// just for that purpose (I've tried to keep shell32 out of this dll).
// Therefore, we read the registry values just like the shell does.
// EricFlo suggested this so it's OK ZAW-wise.
//
LONG lResult = ERROR_SUCCESS;
HKEY hKey = NULL;
DWORD dwDisposition = 0;
const TCHAR szKeyNameRoot[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer");
const TCHAR szMSWinNTDiskQuota[] = TEXT("Microsoft\\Windows NT\\DiskQuota");
LPCTSTR rgpszKeys[] = {
TEXT("\\User Shell Folders"),
TEXT("\\Shell Folders"),
};
//
// Start with an empty path buffer.
//
m_strFilePath.Empty();
for (INT i = 0; i < ARRAYSIZE(rgpszKeys) && m_strFilePath.IsEmpty(); i++)
{
//
// Create the key name.
//
CString strKeyName(szKeyNameRoot);
strKeyName += CString(rgpszKeys[i]);
//
// Open the reg key.
//
lResult = RegCreateKeyEx(HKEY_CURRENT_USER,
strKeyName,
0,
NULL,
0,
KEY_READ,
NULL,
&hKey,
&dwDisposition);
if (ERROR_SUCCESS == lResult)
{
try
{
//
// Get the path to the user's "Application Data" directory.
//
DBGASSERT((NULL != hKey));
DWORD dwValueType = 0;
DWORD cbValue = MAX_PATH * sizeof(TCHAR);
lResult = RegQueryValueEx(hKey,
TEXT("AppData"),
0,
&dwValueType,
(LPBYTE)m_strFilePath.GetBuffer(MAX_PATH),
&cbValue);
m_strFilePath.ReleaseBuffer();
if (ERROR_SUCCESS == lResult)
{
//
// Ensure the path has a trailing backslash.
//
INT cchPath = m_strFilePath.Length();
if (0 < cchPath && TEXT('\\') != m_strFilePath[cchPath-1])
{
m_strFilePath += CString(TEXT("\\"));
}
//
// Append "Microsoft\Windows NT\DiskQuota" to the path.
//
m_strFilePath += CString(szMSWinNTDiskQuota);
}
else
{
//
// Something failed. Ensure m_strFilePath is empty.
//
m_strFilePath.Empty();
if (ERROR_FILE_NOT_FOUND != lResult)
{
DBGERROR((TEXT("SIDCACHE - Error %d getting \"AppData\" reg value."), lResult));
}
}
}
catch(CAllocException& e)
{
lResult = ERROR_OUTOFMEMORY;
}
RegCloseKey(hKey);
}
else if (ERROR_FILE_NOT_FOUND != lResult)
{
DBGERROR((TEXT("SIDCACHE - Error %d opening \"\\User Shell Folders\" or \"Shell Folders\" reg key"), lResult));
}
}
if (!m_strFilePath.IsEmpty())
{
//
// Expand any embedded environment strings.
//
m_strFilePath.ExpandEnvironmentStrings();
//
// Ensure the path DOES NOT have a trailing backslash.
//
INT cchPath = m_strFilePath.Length();
if (0 < cchPath && TEXT('\\') == m_strFilePath[cchPath-1])
{
m_strFilePath[cchPath-1] = TEXT('\0');
}
if ((DWORD)-1 == ::GetFileAttributes(m_strFilePath))
{
//
// If the directory doesn't exist, try to create it.
//
if (0 == CreateCacheFileDirectory(m_strFilePath))
{
//
// Couldn't create the directory, make sure the path
// is empty so we don't try to write to a non-existent
// directory.
//
DBGERROR((TEXT("SIDCACHE - Error %d creating directory \"%s\""),
GetLastError(), (LPCTSTR)m_strFilePath));
m_strFilePath.Empty();
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::CreateCacheFileDirectory
Description: Creates the directory for the SID/Name cache files. Since
the directory lives several levels below "Application Data", we
may need to create several directories before we get to DiskQuota.
Arguments: pszPath - This is a fully-qualified directory path.
Returns: TRUE = Directory created.
FALSE = Directory not created.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
07/21/97 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
BOOL
SidNameCache::CreateCacheFileDirectory(
LPCTSTR pszPath
)
{
DBGTRACE((DM_SIDCACHE, DL_HIGH, TEXT("SidNameCache::CreateCacheFileDirectory")));
BOOL bResult = TRUE;
CString s(pszPath); // Local copy we can play with.
LPTSTR psz = (LPTSTR)s; // Ptr to C string.
while(TEXT('\0') != *psz && bResult)
{
//
// Find the next backslash (or end-of-string).
//
while(TEXT('\0') != *psz && TEXT('\\') != *psz)
{
psz++;
}
//
// Replace backslash with a temporary NUL.
//
TCHAR chSaved = *psz;
*psz = TEXT('\0');
//
// See if the directory already exists.
//
if ((DWORD)-1 == ::GetFileAttributes(s))
{
//
// It doesn't. Try to create it.
//
if (0 == ::CreateDirectory(s, NULL))
{
DBGERROR((TEXT("SIDCACHE - Error %d creating directory \"%s\""),
GetLastError(), (LPCTSTR)s));
bResult = FALSE;
}
}
//
// Replace temp NUL with original backslash and advance ptr
// to next character in path (if we're not at the end of the string).
//
*psz = chSaved;
if (TEXT('\0') != *psz)
{
psz++;
}
}
if (bResult)
{
//
// Created directory. Set SYSTEM & HIDDEN attribute bits on the final
// subdirectory ("\DiskQuota").
//
SetFileAttributes(pszPath,
GetFileAttributes(pszPath) | (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN));
}
return bResult;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::Lock
Description: Obtains an exclusive lock on the cache. This lock is
system-wide so that multiple processes can access the cache files.
Arguments: None.
Returns:
TRUE - Exclusive lock obtained or the lock was abandoned.
No matter how the lock was obtained, the caller should always
check the validity of the index and data files before trusting
their contents.
FALSE - Lock could not be obtained in the required timeout period.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
BOOL
SidNameCache::Lock(
VOID
)
{
BOOL bResult = FALSE;
DBGASSERT((NULL != m_hMutex));
//
// IMPORTANT: Don't try handling thread messages with MsgWaitForMultipleObjects.
// This lock function can be called on the resolver's background
// thread. If you pull up that thread's messages, it won't receive
// the WM_QUIT message commanding it to shutdown.
//
switch(WaitForSingleObject(m_hMutex, MUTEX_WAIT_TIMEOUT))
{
case WAIT_OBJECT_0:
case WAIT_ABANDONED:
bResult = TRUE;
break;
case WAIT_FAILED:
case WAIT_TIMEOUT:
default:
break;
}
return bResult;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::ReleaseLock
Description: Releases the exclusive lock on the cache. This function must
always be paired with a call to Lock(). Be careful of conditions
which may throw an exception and bypass releasing the lock.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::ReleaseLock(
VOID
)
{
DBGASSERT((NULL != m_hMutex));
ReleaseMutex(m_hMutex);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::BeginTransaction
Description: Called at the beginning of any "transaction" to obtain
exclusive access to the cache and verify the cache files are valid.
Although this is of course not true transaction processing, it provides
a simple approximation that is sufficient for this cache implementation.
Note that before returning, the files are "invalidated". After the
transaction is completed, the caller must call EndTransaction to
release the exclusive lock and mark the files as "valid".
Arguments: None.
Returns:
NO_ERROR - Success. Transaction can be carried out.
ERROR_INVALID_DATA (hr) - Index or data file is invalid. Caller should
re-initialize both index and data files.
ERROR_LOCK_FAILED (hr) - Could not obtain exclusive access to the
index and data files. Caller can either repeat the call or
simply fail the cache access.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
HRESULT
SidNameCache::BeginTransaction(
VOID
)
{
HRESULT hResult = NO_ERROR;
if (Lock())
{
if (FilesAreValid())
{
InvalidateFiles();
}
else
{
ReleaseLock();
hResult = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
}
}
else
hResult = HRESULT_FROM_WIN32(ERROR_LOCK_FAILED);
return hResult;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::EndTransaction
Description: Called at the end of any "transaction" to release
exclusive access to the cache and validate the cache files.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::EndTransaction(
VOID
)
{
//
// If you hit this assertion, probably didn't call BeginTransaction
// first.
//
DBGASSERT((!FilesAreValid()));
ValidateFiles();
ReleaseLock();
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::Clear
Description: Resets both the index and data files to an empty state.
Arguments: None.
Returns:
TRUE - Files reset.
FALSE - Couldn't obtain lock to reset files or another process
is also using the cache.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
08/07/00 Clear cache by clearing index and data files. Not BrianAu
destroying and recreating.
*/
///////////////////////////////////////////////////////////////////////////////
BOOL
SidNameCache::Clear(
VOID
)
{
DBGTRACE((DM_SIDCACHE, DL_HIGH, TEXT("SidNameCache::Clear")));
BOOL bResult = FALSE;
CacheAutoLock lock(*this);
if (lock.Lock())
{
if (NULL != m_pIndexMgr && NULL != m_pRecordMgr)
{
m_pIndexMgr->Clear();
m_pRecordMgr->Clear();
ValidateFiles();
bResult = TRUE;
}
}
return bResult;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::FilesAreValid
Description: Examines the guid field in the index and data files.
If the guids are non-zero and are equal, the files are considered
"valid". BeginTransaction sets each guid to all 0's while
EndTransaction fills them with a new GUID.
Arguments: None.
Returns:
TRUE/FALSE indicating file validity.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
BOOL
SidNameCache::FilesAreValid(
VOID
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((INDEX_FILE_MAPPED));
GUID guidIndexFile;
GUID guidDataFile;
m_pIndexMgr->GetFileGUID(&guidIndexFile);
if (GUID_Null != guidIndexFile)
{
m_pRecordMgr->GetFileGUID(&guidDataFile);
return guidDataFile == guidIndexFile;
}
return FALSE; // At least one GUID was all 0's
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::ValidateFiles
Description: Generates a new GUID and writes it to the header of the
index and data files. This should only be called when a transaction
has been successfully completed. EndTransaction calls this method
to mark the files as "valid".
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
05/18/00 Check return value from CoCreateGuid. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::ValidateFiles(
VOID
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((INDEX_FILE_MAPPED));
//
// If GUID creation fails, the cache will be considered invalid.
// That's a reasonable system response. Failure of GUID creation
// is highly unlikely.
//
GUID guid;
if (SUCCEEDED(CoCreateGuid(&guid)))
{
m_pRecordMgr->SetFileGUID(&guid);
m_pIndexMgr->SetFileGUID(&guid);
}
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::InvalidateFiles
Description: Sets the guid field in each file to all 0's. This marks
a file as "invalid". BeginTransaction calls this.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::InvalidateFiles(
VOID
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((INDEX_FILE_MAPPED));
m_pRecordMgr->SetFileGUID(&GUID_Null);
m_pIndexMgr->SetFileGUID(&GUID_Null);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::Lookup
Description: Given a user SID, this method locates the corresponding
data record in the cache and returns the requested information.
(i.e. container, name, full name). Several conditions will cause
the lookup to fail.
1. SID not found in cache.
2. Can't get exclusive lock on cache.
3. Index or data file (or both) are invalid.
4. Record found but expired.
Arguments:
pKeySid - Address of SID to use as lookup key.
ppszContainer [optional] - Address of pointer variable to receive the
address of buffer containing the "container" name string. Caller is
responsible for freeing the buffer with delete[].
May be NULL if the container is not desired.
ppszLogonName [optional] - Address of pointer variable to receive the
address of buffer containing logon name string. Caller is
responsible for freeing the buffer with delete[].
May be NULL if logon name is not desired.
ppszDisplayName [optional] - Address of pointer variable to receive the
address of buffer containing account display string. Caller is
responsible for freeing the buffer with delete[].
May be NULL if display name is not desired.
Returns:
NO_ERROR - Success.
ERROR_FILE_NOT_FOUND (hr) - Sid not found in cache.
ERROR_LOCK_FAILED (hr) - Couldn't get exclusive lock on cache.
ERROR_INVALID_DATA (hr) - Index or data file is invalid.
Exceptions: OutOfMemory
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
HRESULT
SidNameCache::Lookup(
PSID pKeySid,
LPTSTR *ppszContainer,
LPTSTR *ppszLogonName,
LPTSTR *ppszDisplayName
)
{
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::Lookup [SID]")));
DBGASSERT((NULL != pKeySid));
HRESULT hResult = BeginTransaction();
if (SUCCEEDED(hResult))
{
try
{
DWORD iBlock = m_pIndexMgr->Lookup(pKeySid);
if ((DWORD)-1 != iBlock)
{
if (!m_pRecordMgr->RecordExpired(iBlock))
{
PSID pSid = NULL;
//
// This can throw OutOfMemory.
//
hResult = m_pRecordMgr->Retrieve(iBlock,
&pSid,
ppszContainer,
ppszLogonName,
ppszDisplayName);
if (SUCCEEDED(hResult))
DBGASSERT((EqualSid(pSid, pKeySid)));
if (NULL != pSid)
delete[] pSid;
}
else
{
//
// Record is outdated. Delete it from the cache.
// Returning "not found" will cause the caller to get
// a fresh one from the domain controller - which will
// then again be added to the cache.
//
DBGPRINT((DM_SIDCACHE, DL_HIGH,
TEXT("SIDCACHE - Record at block %d has expired."),
iBlock));
m_pIndexMgr->FreeEntry(pKeySid);
m_pRecordMgr->FreeRecord(iBlock);
hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
}
else
{
hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); // SID not in cache.
}
}
catch(CAllocException &e)
{
hResult = E_OUTOFMEMORY;
}
EndTransaction();
}
return hResult;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::Lookup
Description: Given a user's logon name name, this method locates
the corresponding SID in the cache. All comments in the previous
(above) version of this method apply.
Arguments:
pszLogonName - Address of account logon name string.
ppSid - Address of pointer variable to receive the address of buffer
containing the SID. Caller is responsible for freeing the buffer
with delete[].
Returns:
See list in previous method.
Exception: OutOfMemory.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
03/18/98 Replaced domain\name key arguments with single BrianAu
logon name key.
*/
///////////////////////////////////////////////////////////////////////////////
HRESULT
SidNameCache::Lookup(
LPCTSTR pszLogonName,
PSID *ppSid
)
{
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::Lookup [name]")));
DBGASSERT((NULL != pszLogonName));
DBGASSERT((NULL != ppSid));
HRESULT hResult = BeginTransaction();
if (SUCCEEDED(hResult))
{
try
{
//
// Can throw OutOfMemory.
//
DWORD iBlock = m_pIndexMgr->Lookup(pszLogonName);
if ((DWORD)-1 != iBlock)
{
//
// Can throw OutOfMemory.
//
hResult = m_pRecordMgr->Retrieve(iBlock,
ppSid,
NULL,
NULL,
NULL);
if (m_pRecordMgr->RecordExpired(iBlock))
{
//
// Record is outdated. Delete it from the cache.
// Returning "not found" will cause the caller to get
// a fresh one from the domain controller - which will
// then again be added to the cache.
//
DBGASSERT((NULL != *ppSid));
m_pIndexMgr->FreeEntry(*ppSid);
m_pRecordMgr->FreeRecord(iBlock);
delete[] *ppSid;
*ppSid = NULL;
hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
}
else
{
hResult = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); // SID not in cache.
}
}
catch(CAllocException& e)
{
hResult = E_OUTOFMEMORY;
}
EndTransaction();
}
return hResult;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::Add
Description: Add user's information to the cache. Information consists
of the SID (key), container name, account logon name and
account display name.
Arguments:
pSid - Address of user's SID.
pszContainer - Address of account container name string.
i.e. "REDMOND" or "ntdev.microsoft.com\US-SOS ....."
pszLogonName - Address of account logon name string.
i.e. "REDMOND\brianau" or "brianau@microsoft.com"
pszDisplayName - Address of display name string.
i.e. "Brian Aust"
Returns:
NO_ERROR - Success.
S_FALSE - Already exists in cache. Not added.
ERROR_LOCK_FAILED (hr) - Couldn't get exclusive lock on cache.
ERROR_INVALID_DATA (hr) - Index or data file is invalid.
Exceptions: OutOfMemory.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
HRESULT
SidNameCache::Add(
PSID pSid,
LPCTSTR pszContainer,
LPCTSTR pszLogonName,
LPCTSTR pszDisplayName
)
{
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::Add")));
DBGASSERT((NULL != pSid));
DBGASSERT((NULL != pszContainer));
DBGASSERT((NULL != pszLogonName));
DBGASSERT((NULL != pszDisplayName));
HRESULT hResult = BeginTransaction();
if (SUCCEEDED(hResult))
{
try
{
//
// Can throw OutOfMemory.
//
if ((DWORD)-1 == m_pIndexMgr->Lookup(pSid))
{
DWORD iBlock = m_pRecordMgr->Store(pSid,
pszContainer,
pszLogonName,
pszDisplayName);
if ((DWORD)-1 != iBlock)
{
m_pIndexMgr->Add(pSid, iBlock);
hResult = NO_ERROR;
}
}
else
{
hResult = S_FALSE; // Already exists. Not a failure.
}
}
catch(CAllocException& e)
{
hResult = E_OUTOFMEMORY;
}
EndTransaction();
}
return hResult;
}
//***********************************************************************************
//***********************************************************************************
// I N D E X F I L E M A N A G E R
//***********************************************************************************
//***********************************************************************************
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::IndexMgr
Description: Index manager constructor.
Arguments:
refCache - Reference to containing cache object. Used to call
record manager and cache manager public methods.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
SidNameCache::IndexMgr::IndexMgr(
SidNameCache& refCache
) : m_refCache(refCache),
m_pFileHdr(NULL),
m_hFile(NULL),
m_hFileMapping(NULL)
{
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::SidNameCache::IndexMgr")));
//
// Nothing to do.
//
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::~IndexMgr
Description: Index manager destructor.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
SidNameCache::IndexMgr::~IndexMgr(
VOID
)
{
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::SidNameCache::~IndexMgr")));
CloseIndexFile();
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::Initialize
Description: Initializes a new index manager object. If both cBuckets
and cMaxEntries are 0 (default), existing cache files are opened.
Otherwise, new cache files are created.
Arguments:
pszFile - Address of full path for new file.
cBuckets - Number of hash table buckets in index file. Should be
prime.
cMaxEntries - Initial max number of entries for the index. Note
that the index file grows automatically as required.
Returns: Address of mapped file or NULL on failure.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
LPBYTE
SidNameCache::IndexMgr::Initialize(
LPCTSTR pszFile,
DWORD cBuckets,
DWORD cMaxEntries
)
{
DBGTRACE((DM_SIDCACHE, DL_HIGH, TEXT("SidNameCache::IndexMgr::Initialize")));
DBGASSERT((NULL != pszFile));
//
// Store the file name in our CString object.
//
m_strFileName = pszFile;
if (0 == cBuckets && 0 == cMaxEntries)
{
//
// Initialize manager using existing cache files.
//
m_pFileHdr = (PINDEX_FILE_HDR)OpenIndexFile(pszFile);
if (NULL != m_pFileHdr)
{
if (FILE_VERSION != m_pFileHdr->dwVersion ||
INDEX_FILE_SIGNATURE != m_pFileHdr->dwSignature)
{
//
// This version of the software doesn't understand this
// version of the file or file has an invalid signature.
// Don't take any chances. We'll just create a new one.
//
DBGERROR((TEXT("SIDCACHE - Index file is invalid or incorrect version. A new index file will be created.")));
CloseIndexFile();
m_pFileHdr = NULL;
}
}
}
else
{
//
// Initialize manager by creating new cache files.
//
ULARGE_INTEGER uliFileSize;
uliFileSize.QuadPart = FileSize(cMaxEntries, cBuckets);
m_pFileHdr = (PINDEX_FILE_HDR)CreateIndexFile(pszFile,
uliFileSize.HighPart,
uliFileSize.LowPart);
if (NULL != m_pFileHdr)
{
InitNewIndexFile(cBuckets, cMaxEntries);
}
}
return (LPBYTE)m_pFileHdr;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::CreateIndexFile
Description: Creates and initializes a new index file.
Arguments:
pszFile - Address of full path for new file.
cbFileHigh/Low - Size of file in bytes.
Returns: Address of mapped file or NULL on failure.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
LPBYTE
SidNameCache::IndexMgr::CreateIndexFile(
LPCTSTR pszFile,
DWORD cbFileHigh,
DWORD cbFileLow
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::IndexMgr::CreateIndexFile")));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("\tFile: \"%s\""), pszFile ? pszFile : TEXT("<null>")));
DBGASSERT((NULL != pszFile));
CloseIndexFile(); // Make sure any existing index file is closed.
g_pbMappedIndexFile = SidNameCache::OpenMappedFile(
pszFile,
g_pszIndexFileMapping,
CREATE_ALWAYS,
cbFileHigh,
cbFileLow,
&m_hFile,
&m_hFileMapping);
return g_pbMappedIndexFile;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::OpenIndexFile
Description: Opens an existing index file.
Arguments:
pszFile - Address of full path for new file.
Returns: Address of mapped file or NULL on failure.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
LPBYTE
SidNameCache::IndexMgr::OpenIndexFile(
LPCTSTR pszFile
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::IndexMgr::OpenIndexFile")));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("\tFile: \"%s\""), pszFile ? pszFile : TEXT("<null>")));
DBGASSERT((NULL != pszFile));
CloseIndexFile(); // Make sure any existing index file is closed.
g_pbMappedIndexFile = SidNameCache::OpenMappedFile(
pszFile,
g_pszIndexFileMapping,
OPEN_EXISTING,
0,
0,
&m_hFile,
&m_hFileMapping);
return g_pbMappedIndexFile;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::CloseIndexFile
Description: Closes the current index mapping and file.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::IndexMgr::CloseIndexFile(
VOID
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::IndexMgr::CloseIndexFile")));
if (NULL != g_pbMappedIndexFile)
{
UnmapViewOfFile(g_pbMappedIndexFile);
g_pbMappedIndexFile = NULL;
m_pFileHdr = NULL;
}
if (NULL != m_hFileMapping)
{
CloseHandle(m_hFileMapping);
m_hFileMapping = NULL;
}
if (NULL != m_hFile && INVALID_HANDLE_VALUE != m_hFile)
{
CloseHandle(m_hFile);
m_hFile = NULL;
}
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::GrowIndexFile
Description: Increases the size of the current index file.
Arguments:
cGrowEntries - Make room for this many more entries. Note that the
size of the hash table is fixed. If we were to allow this to
change, it would invalidate any existing hash values in the
table (hash code is a function of the SID and table size).
Returns: Address of mapped file or NULL on failure.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
LPBYTE
SidNameCache::IndexMgr::GrowIndexFile(
DWORD cGrowEntries
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::IndexMgr::GrowIndexFile")));
DBGASSERT((INDEX_FILE_MAPPED));
DWORD cOldMaxEntries = m_pFileHdr->cMaxEntries;
DWORD cNewMaxEntries = cOldMaxEntries + cGrowEntries;
DBGPRINT((DM_SIDCACHE, DL_MID,
TEXT("Growing SID cache index %d -> %d entries."),
cOldMaxEntries, cNewMaxEntries));
//
// Open the existing file and map with a new larger size.
// Must calc new size BEFORE closing current index file so that m_pFileHdr
// is still valid.
//
ULARGE_INTEGER uliFileSize;
uliFileSize.QuadPart = FileSize(cNewMaxEntries, m_pFileHdr->cBuckets);
CloseIndexFile();
g_pbMappedIndexFile = SidNameCache::OpenMappedFile(
m_strFileName,
g_pszIndexFileMapping,
OPEN_EXISTING,
uliFileSize.HighPart,
uliFileSize.LowPart,
&m_hFile,
&m_hFileMapping);
m_pFileHdr = (PINDEX_FILE_HDR)g_pbMappedIndexFile;
if (NULL != g_pbMappedIndexFile)
{
m_pFileHdr->cMaxEntries = cNewMaxEntries;
//
// Growing the index only expands the number of index
// pool entries. The index hash table is left alone.
// Good reason to make it large to start with. If we changed
// the hash table size, existing hash codes would be
// invalid.
//
PINDEX_ENTRY pEntry = m_pFileHdr->pEntries + cOldMaxEntries;
for (UINT i = 0; i < cGrowEntries; i++)
{
AddEntryToFreeList(pEntry++);
}
DBGPRINT((DM_SIDCACHE, DL_HIGH, TEXT("SIDCACHE - Index growth complete.")));
}
return g_pbMappedIndexFile;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::InitNewIndexFile
Description: Initializes a new index file filling in the header information
and clearing the index entries.
Arguments:
cBuckets - Number of hash table buckets in index file. Should be
prime.
cMaxEntries - Initial max number of entries for the index. Note
that the index file grows automatically as required.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::IndexMgr::InitNewIndexFile(
DWORD cBuckets,
DWORD cMaxEntries
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::IndexMgr::InitNewIndexFile")));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("\tcBuckets = %d, cMaxEntries = %d"),
cBuckets, cMaxEntries));
DBGASSERT((INDEX_FILE_MAPPED));
m_pFileHdr->dwSignature = INDEX_FILE_SIGNATURE;
m_pFileHdr->dwVersion = FILE_VERSION;
m_pFileHdr->cBuckets = cBuckets;
m_pFileHdr->cMaxEntries = cMaxEntries;
m_pFileHdr->pBuckets = (PINDEX_ENTRY *)(sizeof(INDEX_FILE_HDR));
m_pFileHdr->pEntries = (PINDEX_ENTRY)(m_pFileHdr->pBuckets + cBuckets);
//
// Initialize the hash table and return all entries to the free list.
//
Clear();
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::Clear
Description: Fills the hash table with NULL pointers and returns all
entry nodes to the free list.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::IndexMgr::Clear(
VOID
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::IndexMgr::Clear")));
DBGASSERT((INDEX_FILE_MAPPED));
m_pFileHdr->cEntries = 0;
SetFileGUID(&GUID_Null);
//
// Initialize all hash buckets to NULL.
//
DBGASSERT((0 < m_pFileHdr->cBuckets));
ZeroMemory(NDX_BASED_CAST(BYTE, m_pFileHdr->pBuckets),
m_pFileHdr->cBuckets * sizeof(PINDEX_ENTRY *));
//
// Return all index entry nodes to the free list.
//
PINDEX_ENTRY pEntry = m_pFileHdr->pEntries;
DBGASSERT((0 < m_pFileHdr->cMaxEntries));
for (UINT i = 0; i < m_pFileHdr->cMaxEntries; i++)
{
//
// We're iterating through all entries. No need to detach first.
//
AddEntryToFreeList(pEntry++);
}
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::SetFileGUID
Function: SidNameCache::IndexMgr::GetFileGUID
Description: These functions manipulate the guid field in the index file's
header.
The GUID is used to ensure integrity between
the data and index files. Before any change to either file, the
GUID's are both set to 0. When the change operation is complete,
a new GUID is generated and written to both files. Therefore, before
any transaction, we can validate the data and index files by reading
and comparing GUIDs. If the GUIDs are not 0 and are equal, the
file can be assumed to be valid.
Arguments:
pguid - Address of source or destination GUID.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::IndexMgr::SetFileGUID(
const GUID *pguid
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DBGASSERT((NULL != pguid));
CopyMemory(&m_pFileHdr->guid, pguid, sizeof(GUID));
}
VOID
SidNameCache::IndexMgr::GetFileGUID(
LPGUID pguid
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DBGASSERT((NULL != pguid));
CopyMemory(pguid, &m_pFileHdr->guid, sizeof(GUID));
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::AllocEntry
Description: Allocates an entry from the free list.
Arguments: None.
Returns:
Address of new entry node or NULL if free list is empty.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
PINDEX_ENTRY
SidNameCache::IndexMgr::AllocEntry(
VOID
)
{
DBGASSERT((INDEX_FILE_MAPPED));
PINDEX_ENTRY pEntry = m_pFileHdr->pFirstFree;
if (NULL != pEntry)
{
NDX_BASED(INDEX_ENTRY) *pBasedEntry = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
//
// Unlink the entry from the free list.
//
m_pFileHdr->pFirstFree = pBasedEntry->pNext;
//
// Clear it's "prev" and "next" pointers.
//
pBasedEntry->pNext = pBasedEntry->pPrev = NULL;
if (NULL != m_pFileHdr->pFirstFree)
{
//
// If there is at least one entry in the free list, set the "prev"
// pointer of the new "first" entry to NULL.
//
pBasedEntry = NDX_BASED_CAST(INDEX_ENTRY, m_pFileHdr->pFirstFree);
pBasedEntry->pPrev = NULL;
}
m_pFileHdr->cEntries++;
}
return pEntry;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::FreeEntry
Description: Removes an entry from it's current list and returns it to the
free list. Two versions are provided. One accepts a SID as an entry
identifier, the other accepts the address of the index entry.
Arguments:
pSid - Address of SID associated with entry to be free'd.
pEntry - Address of index entry to be free'd
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::IndexMgr::FreeEntry(
PSID pSid
)
{
DBGASSERT((NULL != pSid));
PINDEX_ENTRY pEntry = Find(pSid);
if (NULL != pEntry)
{
FreeEntry(pEntry);
}
}
VOID
SidNameCache::IndexMgr::FreeEntry(
PINDEX_ENTRY pEntry
)
{
DBGASSERT((NULL != pEntry));
DetachEntry(pEntry);
AddEntryToFreeList(pEntry);
m_pFileHdr->cEntries--;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::AddEntryToFreeList
Description: Returns a detached index entry to the free list.
Arguments:
pEntry - Address of index entry to be added to free list.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::IndexMgr::AddEntryToFreeList(
PINDEX_ENTRY pEntry
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DBGASSERT((NULL != pEntry));
NDX_BASED(INDEX_ENTRY) *pBased = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
//
// Insert the node at the head of the free list.
// Note that double-linking isn't necessary in the free list
// (we always add and remove free list entries at the head)
// therefore we don't need to set the next node's "prev" pointer.
//
pBased->iBucket = (DWORD)-1;
pBased->iBlock = (DWORD)-1;
pBased->pPrev = NULL;
pBased->pNext = m_pFileHdr->pFirstFree;
m_pFileHdr->pFirstFree = pEntry;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::DetachEntry
Description: Detaches an index entry from its current list.
Note that this function assumes that the node exists in a valid
linked list of nodes. Do not call this function on an uninitialized
index entry.
Arguments:
pEntry - Address of index entry to be detached.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
PINDEX_ENTRY
SidNameCache::IndexMgr::DetachEntry(
PINDEX_ENTRY pEntry
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DBGASSERT((NULL != pEntry));
NDX_BASED(INDEX_ENTRY) *pBased = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
NDX_BASED(INDEX_ENTRY) *pBasedNext;
NDX_BASED(INDEX_ENTRY) *pBasedPrev;
//
// Unlink the entry from it's list.
//
if (NULL != pBased->pPrev)
{
pBasedPrev = NDX_BASED_CAST(INDEX_ENTRY, pBased->pPrev);
pBasedPrev->pNext = pBased->pNext;
}
if (NULL != pBased->pNext)
{
pBasedNext = NDX_BASED_CAST(INDEX_ENTRY, pBased->pNext);
pBasedNext->pPrev = pBased->pPrev;
}
//
// If we're detaching the entry that's attached to the hash array element,
// adjust the element's value.
//
if (GetHashBucketValue(pBased->iBucket) == pEntry)
SetHashBucketValue(pBased->iBucket, pBased->pNext);
pBased->pNext = pBased->pPrev = NULL;
return pEntry;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::GetHashBucketValue
Description: Returns the address of the first index entry in a hash bucket.
Arguments:
iBucket - Array index of bucket in hash table array.
Returns:
Address of first entry in bucket's entry list.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
PINDEX_ENTRY
SidNameCache::IndexMgr::GetHashBucketValue(
DWORD iBucket
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DBGASSERT((iBucket < m_pFileHdr->cBuckets));
NDX_BASED(PINDEX_ENTRY) *pBased = NDX_BASED_CAST(PINDEX_ENTRY, m_pFileHdr->pBuckets + iBucket);
return *pBased;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::SetHashBucketValue
Description: Sets the address of the first index entry in a hash bucket.
OK to set it as NULL.
Arguments:
iBucket - Array index of bucket in hash table array.
pEntry - Address of entry node.
Returns:
Address of first entry in bucket's entry list.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::IndexMgr::SetHashBucketValue(
DWORD iBucket,
PINDEX_ENTRY pEntry
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DBGASSERT((iBucket < m_pFileHdr->cBuckets));
//
// pEntry == NULL is OK.
//
NDX_BASED(PINDEX_ENTRY) *pBased = NDX_BASED_CAST(PINDEX_ENTRY,
m_pFileHdr->pBuckets + iBucket);
*pBased = pEntry;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::Find (3 overloaded methods)
Description: Given either a SID, a SID and hash value, or a user logon name,
this method returns the address of the index entry
representing that cache entry.
Arguments:
pKeySid - Address of SID to use as a lookup key.
dwHashCode - Result of calling Hash(pKeySid).
pszKeyLogonName - Address of account logon name string.
Returns:
Address of index entry representing the user.
NULL if not found.
Exceptions: OutOfMemory.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
PINDEX_ENTRY
SidNameCache::IndexMgr::Find(
PSID pKeySid
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DBGASSERT((NULL != pKeySid));
return Find(pKeySid, Hash(pKeySid)); // Can throw OutOfMemory.
}
PINDEX_ENTRY
SidNameCache::IndexMgr::Find(
PSID pKeySid,
DWORD dwHashCode
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DBGASSERT((NULL != pKeySid));
BOOL bFound = FALSE;
PINDEX_ENTRY pEntry = GetHashBucketValue(dwHashCode);
while(NULL != pEntry && !bFound)
{
PSID pSid = NULL;
NDX_BASED(INDEX_ENTRY) *pBased = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
//
// This can throw OutOfMemory.
//
if (SUCCEEDED(m_refCache.m_pRecordMgr->Retrieve(pBased->iBlock,
&pSid,
NULL,
NULL,
NULL)))
{
if (EqualSid(pKeySid, pSid))
{
bFound = TRUE;
}
}
if (!bFound)
{
pEntry = pBased->pNext;
}
delete[] pSid;
}
return pEntry;
}
//
// This version of Find() performs a linear search of the index to locate
// the specified logon name. The cache is currently indexed only
// on user SID because this is the only key used for very rapid lookups.
// The cache implementation could be easily extended to include a
// logon name index. All that's needed is a second hash bucket array,
// a hash-on-name function and some adjustments to the placement of
// file data. I just don't think the benefit is worth the cost.
//
PINDEX_ENTRY
SidNameCache::IndexMgr::Find(
LPCTSTR pszKeyLogonName
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DBGASSERT((NULL != pszKeyLogonName));
BOOL bFound = FALSE;
PINDEX_ENTRY pEntry = NULL;
for (UINT i = 0; !bFound && (i < m_pFileHdr->cBuckets); i++)
{
pEntry = GetHashBucketValue(i);
while(NULL != pEntry && !bFound)
{
array_autoptr<TCHAR> ptrLogonName;
NDX_BASED(INDEX_ENTRY) *pBased = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
//
// This can throw OutOfMemory.
//
if (SUCCEEDED(m_refCache.m_pRecordMgr->Retrieve(pBased->iBlock,
NULL,
NULL, // no container.
ptrLogonName.getaddr(),
NULL))) // no display name
{
DBGASSERT((NULL != ptrLogonName.get()));
if (0 == lstrcmpi(ptrLogonName.get(), pszKeyLogonName))
{
bFound = TRUE;
}
}
if (!bFound)
{
pEntry = pBased->pNext;
}
}
}
return pEntry;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::Lookup (2 overloaded methods)
Description: Given either a SID or an account logon name,
this method returns the starting block index of the corresponding
record in the data file.
Arguments:
pSid - Address of SID to use as a lookup key.
pszLogonName - Address of account logon name string.
Returns:
Index of the starting block for the record in the data file.
(DWORD)-1 if the record is not found.
Exceptions: OutOfMemory.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
DWORD
SidNameCache::IndexMgr::Lookup(
PSID pSid
)
{
DBGASSERT((NULL != pSid));
//
// This can throw OutOfMemory.
//
PINDEX_ENTRY pEntry = Find(pSid, Hash(pSid));
if (NULL != pEntry)
{
NDX_BASED(INDEX_ENTRY) *pBased = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
return pBased->iBlock;
}
return (DWORD)-1;
}
DWORD
SidNameCache::IndexMgr::Lookup(
LPCTSTR pszLogonName
)
{
DBGASSERT((NULL != pszLogonName));
//
// This can throw OutOfMemory.
//
PINDEX_ENTRY pEntry = Find(pszLogonName);
if (NULL != pEntry)
{
NDX_BASED(INDEX_ENTRY) *pBased = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
return pBased->iBlock;
}
return (DWORD)-1;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::Add
Description: Adds a SID/data-file-index pair to the index file. This
entry can later be used to locate the SID's record in the data file.
Arguments:
pSid - Address of SID to use as a lookup key.
iBlock - Index of the starting block for the SID's record in the
data file.
Returns: Address of the item's new index entry.
Exceptions: OutOfMemory.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
PINDEX_ENTRY
SidNameCache::IndexMgr::Add(
PSID pSid,
DWORD iBlock
)
{
DWORD dwHashCode = Hash(pSid);
PINDEX_ENTRY pEntry = Find(pSid, dwHashCode); // Can throw OutOfMemory.
//
// Don't create duplicate entries.
//
if (NULL == pEntry)
{
//
// Try to allocate an index entry from the free list.
//
pEntry = AllocEntry();
if (NULL == pEntry)
{
//
// Grow the index file and try again.
//
GrowIndexFile(INDEX_FILE_GROW_ENTRIES);
pEntry = AllocEntry();
}
if (NULL != pEntry)
{
NDX_BASED(INDEX_ENTRY) *pBasedEntry = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
NDX_BASED(INDEX_ENTRY) *pBasedNext;
//
// Fill in the members of the new entry.
//
pBasedEntry->iBucket = dwHashCode;
pBasedEntry->iBlock = iBlock;
pBasedEntry->pNext = GetHashBucketValue(dwHashCode);
pBasedEntry->pPrev = NULL;
//
// Now insert it at the head of the hash bucket's entry list.
//
if (NULL != pBasedEntry->pNext)
{
pBasedNext = NDX_BASED_CAST(INDEX_ENTRY, pBasedEntry->pNext);
pBasedNext->pPrev = pEntry;
}
SetHashBucketValue(dwHashCode, pEntry);
}
}
return pEntry;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::Hash
Description: Given a SID, this method calculates a hash value to be used
as an offset into the index's hash table bucket array. The
algorithm simply sums the value of the SID's bytes. The resulting
hash code is this sum modulo the size of the hash table. For this
simple algorithm to be effective, it is important that the hash
table size be a prime number.
Here's some representative primes: 101, 503, 1009, 5003, 10007
Arguments:
pSid - Address of SID to use as an index lookup key.
Returns: Hashed SID. The value will be between 0 and m_pFileHdr->cBuckets.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
DWORD
SidNameCache::IndexMgr::Hash(
PSID pSid
)
{
DBGASSERT((INDEX_FILE_MAPPED));
DWORD dwCode = 0;
PBYTE pbSid = (PBYTE)pSid;
PBYTE pbEndSid = pbSid + GetLengthSid(pSid);
for ( ;pbSid < pbEndSid; pbSid++)
dwCode += *pbSid;
return dwCode % m_pFileHdr->cBuckets;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IndexMgr::Dump [DEBUG only]
Description: Dumps the contents of the index file to the debugger output.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
#if DBG
VOID
SidNameCache::IndexMgr::Dump(
VOID
)
{
UINT i, j;
DBGASSERT((INDEX_FILE_MAPPED));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("Dumping SidNameCache IndexMgr at 0x%p"), this));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" Base...............: 0x%p"), g_pbMappedIndexFile));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" m_pFileHdr.........: 0x%p"), m_pFileHdr));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" dwSignature......: 0x%08X"), (DWORD)m_pFileHdr->dwSignature));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" dwVersion........: 0x%08X"), (DWORD)m_pFileHdr->dwVersion));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" cBuckets.........: %d"), (DWORD)m_pFileHdr->cBuckets));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" cMaxEntries......: %d"), (DWORD)m_pFileHdr->cMaxEntries));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" cEntries.........: %d"), (DWORD)m_pFileHdr->cEntries));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" pBuckets.........: 0x%p"), m_pFileHdr->pBuckets));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" pEntries.........: 0x%p"), m_pFileHdr->pEntries));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" pFirstFree.......: 0x%p"), m_pFileHdr->pFirstFree));
for (i = 0; i < m_pFileHdr->cBuckets; i++)
{
PINDEX_ENTRY pEntry = GetHashBucketValue(i);
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" Bucket[%03d] = 0x%p"), i, pEntry));
while(NULL != pEntry)
{
NDX_BASED(INDEX_ENTRY) *pBased = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" Bkt = %3d P = 0x%08X N = 0x%08X Blk = %d"),
pBased->iBucket,
pBased->pPrev,
pBased->pNext,
pBased->iBlock));
pEntry = pBased->pNext;
}
}
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" FreeList")));
PINDEX_ENTRY pEntry = m_pFileHdr->pFirstFree;
while(NULL != pEntry)
{
NDX_BASED(INDEX_ENTRY) *pBased = NDX_BASED_CAST(INDEX_ENTRY, pEntry);
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" Bkt = %3d P = 0x%08X N = 0x%08X Blk = %d"),
pBased->iBucket,
pBased->pPrev,
pBased->pNext,
pBased->iBlock));
pEntry = pBased->pNext;
}
}
#endif
//***********************************************************************************
//***********************************************************************************
// D A T A F I L E M A N A G E R
//***********************************************************************************
//***********************************************************************************
//
// Default cache record life values in days.
//
const DWORD DEF_REC_LIFE_MIN = 30;
const DWORD DEF_REC_LIFE_MAX = 60;
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::RecordMgr
Description: Record manager constructor.
Arguments:
refCache - Reference to containing cache object. Used to call
index manager and cache manager public methods.
Returns: Nothing.
Exceptions: OutOfMemory.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
SidNameCache::RecordMgr::RecordMgr(
SidNameCache& refCache
) : m_refCache(refCache),
m_pFileHdr(NULL),
m_hFile(NULL),
m_hFileMapping(NULL),
m_cDaysRecLifeMin(DEF_REC_LIFE_MIN),
m_cDaysRecLifeRange(DEF_REC_LIFE_MAX - DEF_REC_LIFE_MIN)
{
DWORD cDaysRecLifeMax = DEF_REC_LIFE_MAX;
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::SidNameCache::RecordMgr")));
//
// Retrieve the min/max record life (days) from the registry.
// Note that we store record life min and range since those are
// what's used in the record life calculation. Seems more
// self-explanatory to store min/max in the registry rather than
// min/range.
//
RegKey key(HKEY_CURRENT_USER, REGSTR_KEY_DISKQUOTA);
if (key.Open(KEY_WRITE, true))
{
if (FAILED(key.GetValue(g_szSidCacheRecLifeMin, &m_cDaysRecLifeMin)) ||
65536 <= m_cDaysRecLifeMin)
{
m_cDaysRecLifeMin = DEF_REC_LIFE_MIN; // Default;
key.SetValue(g_szSidCacheRecLifeMin, m_cDaysRecLifeMin);
}
if (FAILED(key.GetValue(g_szSidCacheRecLifeMax, &cDaysRecLifeMax)) ||
65536 <= cDaysRecLifeMax)
{
cDaysRecLifeMax = DEF_REC_LIFE_MAX; // Default;
key.SetValue(g_szSidCacheRecLifeMax, cDaysRecLifeMax);
}
}
if (cDaysRecLifeMax < m_cDaysRecLifeMin)
cDaysRecLifeMax = m_cDaysRecLifeMin;
m_cDaysRecLifeRange = cDaysRecLifeMax - m_cDaysRecLifeMin;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::~RecordMgr
Description: Record manager destructor. Closes the data file.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
SidNameCache::RecordMgr::~RecordMgr(
VOID
)
{
DBGTRACE((DM_SIDCACHE, DL_MID, TEXT("SidNameCache::SidNameCache::~RecordMgr")));
CloseDataFile();
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::Initialize
Description: Initializes a new record manager object.
Arguments:
pszFile - Address of full path for new file.
cBlocks - Number of storage blocks in data file. Each is 32 bytes.
If this argument is 0, the function tries to open an existing
cache data file.
Returns: Address of mapped file or NULL on failure.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
LPBYTE
SidNameCache::RecordMgr::Initialize(
LPCTSTR pszFile,
DWORD cBlocks
)
{
DBGTRACE((DM_SIDCACHE, DL_HIGH, TEXT("SidNameCache::RecordMgr::Initialize")));
DBGPRINT((DM_SIDCACHE, DL_HIGH, TEXT("\tpszFile = \"%s\", cBlocks = %d"),
pszFile ? pszFile : TEXT("<null>"), cBlocks));
//
// Store the file name in our CString object.
//
m_strFileName = pszFile;
if (0 != cBlocks)
{
//
// Create a new data file.
//
// cBlocks must be a multiple of 32.
// This satisfies the quadword alignment and makes sizing the
// allocation bitmap much easier. We let the caller pass in any value
// they want and we just adjust it upward as needed.
//
if (cBlocks & 0x0000001F)
cBlocks = (cBlocks & 0xFFFFFFE0) + 32;
ULARGE_INTEGER uliFileSize;
uliFileSize.QuadPart = FileSize(cBlocks);
m_pFileHdr = (PDATA_FILE_HDR)CreateDataFile(pszFile,
uliFileSize.HighPart,
uliFileSize.LowPart);
if (NULL != m_pFileHdr)
{
InitNewDataFile(cBlocks);
}
}
else
{
//
// Open an existing data file.
//
m_pFileHdr = (PDATA_FILE_HDR)OpenDataFile(pszFile);
if (NULL != m_pFileHdr)
{
if (FILE_VERSION != m_pFileHdr->dwVersion ||
DATA_FILE_SIGNATURE != m_pFileHdr->dwSignature)
{
//
// This version of the software doesn't understand this
// version of the file or the signature is invalid.
// Don't take any chances. We'll just create a new one.
//
DBGERROR((TEXT("SIDCACHE - Data file is invalid or incorrect version. A new data file will be created.")));
CloseDataFile();
m_pFileHdr = NULL;
}
}
}
return (LPBYTE)m_pFileHdr;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::CreateDataFile
Description: Creates and initializes a new data file.
Arguments:
pszFile - Address of full path for new file.
cbFileHigh/Low - Size of file in bytes.
Returns: Address of mapped file or NULL on failure.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
LPBYTE
SidNameCache::RecordMgr::CreateDataFile(
LPCTSTR pszFile,
DWORD cbFileHigh,
DWORD cbFileLow
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::RecordMgr::CreateDataFile")));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("\tpszFile = \"%s\""),
pszFile ? pszFile : TEXT("<null>")));
CloseDataFile(); // Make sure any existing data file is closed.
g_pbMappedDataFile = SidNameCache::OpenMappedFile(
pszFile,
g_pszDataFileMapping,
CREATE_ALWAYS,
cbFileHigh,
cbFileLow,
&m_hFile,
&m_hFileMapping);
return g_pbMappedDataFile;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::OpenDataFile
Description: Opens an existing data file.
Arguments:
pszFile - Address of full path of existing file.
Returns: Address of mapped file or NULL on failure.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
LPBYTE
SidNameCache::RecordMgr::OpenDataFile(
LPCTSTR pszFile
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::RecordMgr::OpenDataFile")));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("\tpszFile = \"%s\""),
pszFile ? pszFile : TEXT("<null>")));
CloseDataFile(); // Make sure any existing data file is closed.
g_pbMappedDataFile = SidNameCache::OpenMappedFile(
pszFile,
g_pszDataFileMapping,
OPEN_EXISTING,
0,
0,
&m_hFile,
&m_hFileMapping);
return g_pbMappedDataFile;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::CloseDataFile
Description: Closes the current data mapping and file.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::RecordMgr::CloseDataFile(
VOID
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::RecordMgr::CloseDataFile")));
if (NULL != g_pbMappedDataFile)
{
UnmapViewOfFile(g_pbMappedDataFile);
g_pbMappedDataFile = NULL;
m_pFileHdr = NULL;
}
if (NULL != m_hFileMapping)
{
CloseHandle(m_hFileMapping);
m_hFileMapping = NULL;
}
if (NULL != m_hFile && INVALID_HANDLE_VALUE != m_hFile)
{
CloseHandle(m_hFile);
m_hFile = NULL;
}
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::GrowDataFile
Description: Increases the size of the current data file.
Arguments:
cGrowBlocks - Add this many more blocks to the data file. The
block allocation bitmap is also extended to accomdate the new
block count.
Returns: Address of mapped file or NULL on failure.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
LPBYTE
SidNameCache::RecordMgr::GrowDataFile(
DWORD cGrowBlocks
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::RecordMgr::GrowDataFile")));
DBGASSERT((DATA_FILE_MAPPED));
DWORD cOldBlocks = m_pFileHdr->cBlocks;
DWORD cNewBlocks = cOldBlocks + cGrowBlocks;
DWORD cOldMapEle = m_pFileHdr->cMapElements;
DWORD cNewMapEle = 0; // Will calc later.
//
// cBlocks must be a multiple of 32.
// This satisfies the quadword alignment and makes sizing the
// allocation bitmap much easier.
//
if (cNewBlocks & 0x0000001F)
cNewBlocks = (cNewBlocks & 0xFFFFFFE0) + 32;
DBGASSERT((cNewBlocks >= cOldBlocks));
//
// Adjust cGrowBlocks for any adjustments in cNewBlocks.
//
cGrowBlocks = cNewBlocks - cOldBlocks;
//
// How many alloc map elements (DWORDs) do we need now?
//
cNewMapEle = cNewBlocks / BITS_IN_DWORD;
DBGPRINT((DM_SIDCACHE, DL_MID,
TEXT("Growing SID cache data file\nMap Ele %d -> %d\nBlocks %d -> %d"),
cOldMapEle, cNewMapEle, cOldBlocks, cNewBlocks));
//
// Open the mapped file with a new larger size.
//
ULARGE_INTEGER uliFileSize;
uliFileSize.QuadPart = FileSize(cNewBlocks);
CloseDataFile();
g_pbMappedDataFile = SidNameCache::OpenMappedFile(
m_strFileName,
g_pszDataFileMapping,
OPEN_EXISTING,
uliFileSize.HighPart,
uliFileSize.LowPart,
&m_hFile,
&m_hFileMapping);
m_pFileHdr = (PDATA_FILE_HDR)g_pbMappedDataFile;
if (NULL != g_pbMappedDataFile)
{
UINT i = 0;
//
// Block count and map size both increase.
// Since map size increases, blocks must be moved to accomodate new
// map elements. Since the index file tracks records by block index,
// this movement doesn't affect existing index file entries.
//
m_pFileHdr->cBlocks = cNewBlocks;
m_pFileHdr->cMapElements = cNewMapEle;
//
// Save current block base for when we move the blocks to make room for
// the growth of the allocation bitmap.
//
PBLOCK pBlocksOld = m_pFileHdr->pBlocks;
//
// Calculate the new address of block 0.
// We want all of the data blocks quadword aligned because they contain
// a FILETIME structure (64-bits).
//
m_pFileHdr->pBlocks = (PBLOCK)(m_pFileHdr->pdwMap + m_pFileHdr->cMapElements);
QuadAlign((LPDWORD)(&m_pFileHdr->pBlocks));
//
// Move all of the existing blocks to their new locations.
//
MoveMemory(DAT_BASED_CAST(BLOCK, m_pFileHdr->pBlocks),
DAT_BASED_CAST(BLOCK, pBlocksOld),
cOldBlocks * sizeof(BLOCK));
//
// Initialize the new map elements to 0 (un-allocated).
//
ZeroMemory(DAT_BASED_CAST(BYTE, m_pFileHdr->pdwMap + cOldMapEle),
(cNewMapEle - cOldMapEle) * sizeof(DWORD));
//
// Initialize the new data blocks to 0xCC pattern.
//
FillBlocks(cOldBlocks, cGrowBlocks, RECORD_UNUSED_BYTE);
DBGPRINT((DM_SIDCACHE, DL_MID, TEXT("SIDCACHE - Data file growth complete.")));
}
return g_pbMappedDataFile;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::SetFileGUID
Function: SidNameCache::RecordMgr::GetFileGUID
Description: These functions manipulate the guid field in the data file's
header.
The GUID is used to ensure integrity between
the data and index files. Before any change to either file, the
GUID's are both set to 0. When the change operation is complete,
a new GUID is generated and written to both files. Therefore, before
any transaction, we can validate the data and index files by reading
and comparing GUIDs. If the GUIDs are not 0 and are equal, the
file can be assumed to be valid.
Arguments:
pguid - Address of source or destination GUID.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::RecordMgr::SetFileGUID(
const GUID *pguid
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((NULL != pguid));
CopyMemory(&m_pFileHdr->guid, pguid, sizeof(GUID));
}
VOID
SidNameCache::RecordMgr::GetFileGUID(
LPGUID pguid
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((NULL != pguid));
CopyMemory(pguid, &m_pFileHdr->guid, sizeof(GUID));
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::InitNewDataFile
Description: Initializes a new data file filling in the header information
and writing 0xCC in all the data block bytes.
Arguments:
cBlocks - Number of data blocks (32 bytes each) in data file.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::RecordMgr::InitNewDataFile(
DWORD cBlocks
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::RecordMgr::InitNewDataFile")));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("\tcBlocks = %d"), cBlocks));
UINT i = 0;
DBGASSERT((DATA_FILE_MAPPED));
//
// Initialize file header.
//
m_pFileHdr->dwSignature = DATA_FILE_SIGNATURE;
m_pFileHdr->dwVersion = FILE_VERSION;
m_pFileHdr->cBlocks = cBlocks;
m_pFileHdr->cMapElements = cBlocks / BITS_IN_DWORD;
m_pFileHdr->pdwMap = (LPDWORD)(sizeof(DATA_FILE_HDR));
m_pFileHdr->pBlocks = (PBLOCK)(m_pFileHdr->pdwMap + m_pFileHdr->cMapElements);
//
// We want all of the data blocks quadword aligned because they contain
// a FILETIME structure (64-bits).
//
QuadAlign((LPDWORD)(&m_pFileHdr->pBlocks));
//
// Write 0xCC to all data block bytes.
//
Clear();
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::Clear
Description: Zero's the file header guid, clears all bits in the
block allocation bitmap and fills the data blocks with 0xCC.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::RecordMgr::Clear(
VOID
)
{
DBGTRACE((DM_SIDCACHE, DL_LOW, TEXT("SidNameCache::RecordMgr::Clear")));
DBGASSERT((DATA_FILE_MAPPED));
m_pFileHdr->cBlocksUsed = 0;
m_pFileHdr->iFirstFree = 0;
SetFileGUID(&GUID_Null);
//
// Initialize all block allocation map bits to 0 (un-allocated).
//
ZeroMemory(DAT_BASED_CAST(BYTE, m_pFileHdr->pdwMap),
m_pFileHdr->cMapElements * sizeof(DWORD));
//
// Initialize all data blocks to 0xCC pattern.
//
FillBlocks(0, m_pFileHdr->cBlocks, RECORD_UNUSED_BYTE);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::FillBlocks
Description: Fills a range of blocks with a specified byte.
Arguments:
iBlock - Index of first block in range.
cBlocks - Number of blocks to fill.
b - Byte to write to blocks.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::RecordMgr::FillBlocks(
DWORD iBlock,
DWORD cBlocks,
BYTE b
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((ValidBlockNumber(iBlock)));
DAT_BASED(BYTE) *pb = DAT_BASED_CAST(BYTE, BlockAddress(iBlock));
DBGASSERT((SidNameCache::IsQuadAligned(pb)));
//
// Just in case the fill request would extend over the
// end of the file, truncate the requested block count. The assertion
// will catch it during development.
//
DWORD iLastBlock = iBlock + cBlocks - 1;
DBGASSERT((ValidBlockNumber(iLastBlock)));
if (iLastBlock >= m_pFileHdr->cBlocks)
cBlocks = m_pFileHdr->cBlocks - iBlock;
FillMemory(pb, sizeof(BLOCK) * cBlocks, b);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::IsBitSet
Function: SidNameCache::RecordMgr::SetBit
Function: SidNameCache::RecordMgr::ClrBit
Description: Tests and sets bits in the block allocation bitmap.
Arguments:
pdwBase - Address of 1st DWORD in bitmap.
iBit - 0-based index of bit in bitmap.
Returns: IsBitSet returns TRUE/FALSE indicating the state of the bit.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
BOOL
SidNameCache::RecordMgr::IsBitSet(
LPDWORD pdwBase,
DWORD iBit
)
{
DBGASSERT((NULL != pdwBase));
DWORD b = iBit & 0x0000001F;
DWORD i = iBit >> 5;
return (*(pdwBase + i)) & (1 << b);
}
VOID
SidNameCache::RecordMgr::SetBit(
LPDWORD pdwBase,
DWORD iBit
)
{
DBGASSERT((NULL != pdwBase));
DWORD b = iBit & 0x0000001F;
DWORD i = iBit >> 5;
(*(pdwBase + i)) |= (1 << b);
}
VOID
SidNameCache::RecordMgr::ClrBit(
LPDWORD pdwBase,
DWORD iBit
)
{
DBGASSERT((NULL != pdwBase));
DWORD b = iBit & 0x0000001F;
DWORD i = iBit >> 5;
(*(pdwBase + i)) &= ~(1 << b);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::ValidBlockNumber
Description: Determine if a given block number is valid for the
current data file. Primarily meant for use in assertions.
Arguments:
iBlock - 0-based index of block in data file.
Returns: TRUE/FALSE indicating validity of block number.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
BOOL
SidNameCache::RecordMgr::ValidBlockNumber(
DWORD iBlock
)
{
DBGASSERT((DATA_FILE_MAPPED));
return (iBlock < m_pFileHdr->cBlocks);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::IsBlockUsed
Function: SidNameCache::RecordMgr::MarkBlockUsed
Function: SidNameCache::RecordMgr::MarkBlockUnused
Description: Examines and changes the allocation state of a block in the
data file.
Arguments:
iBlock - 0-based index of block in data file.
Returns: IsBlockUsed returns TRUE/FALSE indicating the allocation state.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
BOOL
SidNameCache::RecordMgr::IsBlockUsed(
DWORD iBlock
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((ValidBlockNumber(iBlock)));
DAT_BASED(DWORD) *pdwBased = DAT_BASED_CAST(DWORD, m_pFileHdr->pdwMap);
return IsBitSet((LPDWORD)pdwBased, iBlock);
}
VOID
SidNameCache::RecordMgr::MarkBlockUsed(
DWORD iBlock
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((ValidBlockNumber(iBlock)));
DAT_BASED(DWORD) *pdwBased = DAT_BASED_CAST(DWORD, m_pFileHdr->pdwMap);
DBGASSERT((!IsBitSet((LPDWORD)pdwBased, iBlock)));
SetBit((LPDWORD)pdwBased, iBlock);
}
VOID
SidNameCache::RecordMgr::MarkBlockUnused(
DWORD iBlock
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((ValidBlockNumber(iBlock)));
DAT_BASED(DWORD) *pdwBased = DAT_BASED_CAST(DWORD, m_pFileHdr->pdwMap);
DBGASSERT((IsBitSet((LPDWORD)pdwBased, iBlock)));
ClrBit((LPDWORD)pdwBased, iBlock);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::BlocksRequired
Description: Calculate the number of blocks required to store a given
number of bytes.
Arguments:
cb - Number of bytes requested.
Returns: Number of 32-byte blocks required to store the bytes.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
DWORD
SidNameCache::RecordMgr::BlocksRequired(
DWORD cb
)
{
//
// Round byte request up to nearest 32-byte block.
//
if (cb & 0x0000001F)
cb = (cb & 0xFFFFFFE0) + 32;
//
// How many "blocks" are required?
//
return BYTE_TO_BLOCK(cb);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::IsQuadAligned
Function: SidNameCache::QuadAlign
Function: SidNameCache::WordAlign
Description: Methods that determine if a value is quad aligned and for
updating a value so that it is quad or word aligned.
Arguments:
See the individual methods. It's pretty self-explanatory.
Returns:
See the individual methods. It's pretty self-explanatory.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
BOOL
SidNameCache::IsQuadAligned(
LPVOID pv
)
{
return (IsQuadAligned((DWORD_PTR)pv));
}
BOOL
SidNameCache::IsQuadAligned(
DWORD_PTR dw
)
{
return (0 == (dw & 0x00000007));
}
VOID
SidNameCache::QuadAlign(
LPDWORD pdwValue
)
{
DBGASSERT((NULL != pdwValue));
if (*pdwValue & 0x00000007)
{
//
// Round up to next whole multiple of 8.
//
*pdwValue = (*pdwValue & 0xFFFFFFF8) + 8;
}
}
VOID
SidNameCache::WordAlign(
LPDWORD pdwValue
)
{
DBGASSERT((NULL != pdwValue));
if (*pdwValue & 0x00000001)
{
//
// Round up to next whole multiple of 2.
//
*pdwValue = (*pdwValue & 0xFFFFFFFE) + 2;
}
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::BytesRequiredForRecord
Description: Calculates the number of bytes required to store a given
data file record. Optionally returns the required size for each
field in the record (SID, name, domain etc). The function makes
allowances for any required data type alignments in the file.
Arguments:
pSid - Address of user's SID.
pcbSid [optional] - Address of variable to receive required SID length.
pszContainer - Address of user's account container name string.
pcbContainer [optional] - Address of variable to receive required length
of container name string.
pszLogonName - Address of user's logon name string.
pcbLogonName [optional] - Address of variable to receive required length
of logon name string.
pszDisplayName - Address of user's display name string.
pcbDisplayName [optional] - Address of variable to receive required
length of display name string.
Returns: Number of bytes required to store the record.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
DWORD
SidNameCache::RecordMgr::BytesRequiredForRecord(
PSID pSid,
LPDWORD pcbSid,
LPCTSTR pszContainer,
LPDWORD pcbContainer,
LPCTSTR pszLogonName,
LPDWORD pcbLogonName,
LPCTSTR pszDisplayName,
LPDWORD pcbDisplayName
)
{
DWORD cb = 0;
DWORD cbTotal = sizeof(RECORD_HDR);
//
// SID follows the header so it IS quadword aligned.
// It's a byte structure so it doesn't have to be; but it is anyway.
//
cb = GetLengthSid(pSid);
cbTotal += cb;
if (NULL != pcbSid)
*pcbSid = cb;
//
// Strings are UNICODE and must be word-aligned. Just align the first.
// All subsequent are guaranteed to be properly aligned.
//
SidNameCache::WordAlign(&cbTotal);
cb = (lstrlen(pszContainer) + 1) * sizeof(TCHAR);
cbTotal += cb;
if (NULL != pcbContainer)
*pcbContainer = cb;
cb = (lstrlen(pszLogonName) + 1) * sizeof(TCHAR);
cbTotal += cb;
if (NULL != pcbLogonName)
*pcbLogonName = cb;
cb = (lstrlen(pszDisplayName) + 1) * sizeof(TCHAR);
cbTotal += cb;
if (NULL != pcbDisplayName)
*pcbDisplayName = cb;
return cbTotal;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::FreeBlock
Description: Frees a single block in the data file.
Arguments:
iBlock - 0-based index of the block.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::RecordMgr::FreeBlock(
DWORD iBlock
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((ValidBlockNumber(iBlock)));
DBGASSERT((IsBlockUsed(iBlock)));
MarkBlockUnused(iBlock);
DBGASSERT((!IsBlockUsed(iBlock)));
FillBlocks(iBlock, 1, RECORD_UNUSED_BYTE);
//
// Update the "first free" index if needed.
//
if (iBlock < m_pFileHdr->iFirstFree)
m_pFileHdr->iFirstFree = iBlock;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::FreeBlocks
Description: Frees a series of contiguous blocks in the data file.
Arguments:
iBlock - 0-based index of the first block in the series.
cBlocks - Number of blocks in the series.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::RecordMgr::FreeBlocks(
DWORD iFirstBlock,
DWORD cBlocks
)
{
for (UINT i = 0; i < cBlocks; i++)
FreeBlock(iFirstBlock + i);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::FreeRecord
Description: Frees all blocks in a data file record.
Arguments:
iFirstBlock - 0-based index of the first block in the record.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
VOID
SidNameCache::RecordMgr::FreeRecord(
DWORD iFirstBlock
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((ValidBlockNumber(iFirstBlock)));
DAT_BASED(RECORD_HDR) *pRecHdr = DAT_BASED_CAST(RECORD_HDR, BlockAddress(iFirstBlock));
DBGASSERT((RECORD_SIGNATURE == pRecHdr->dwSignature));
FreeBlocks(iFirstBlock, pRecHdr->cBlocks);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::BlockAddress
Description: Calculates the non-based address of a given block in the
data file.
Arguments:
iBlock - 0-based index of the block.
Returns: Address of the block in the data file.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
PBLOCK
SidNameCache::RecordMgr::BlockAddress(
DWORD iBlock
)
{
DBGASSERT((DATA_FILE_MAPPED));
DBGASSERT((ValidBlockNumber(iBlock)));
return m_pFileHdr->pBlocks + iBlock;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::FileSize
Description: Calculate the data file size required for a given number of
data blocks. Accounts for data type alignment.
Arguments:
cBlocks - Number of blocks required in the data file.
Returns: Bytes required.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/24/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
UINT64
SidNameCache::RecordMgr::FileSize(
DWORD cBlocks
)
{
DWORD dwTemp = sizeof(DATA_FILE_HDR) +
((cBlocks / BITS_IN_DWORD) * sizeof(DWORD));
//
// Start of blocks must be quad-aligned.
//
SidNameCache::QuadAlign(&dwTemp);
return (UINT64)(dwTemp) +
(UINT64)(sizeof(BLOCK) * cBlocks);
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::AllocBlocks
Description: Allocates a specified number of contiguous blocks in the
data file.
Arguments:
cBlocksReqd - Number of blocks required in the allocation.
Returns: If successful, returns the index of the first block in the
allocation. Returns (DWORD)-1 if the block's can't be allocated.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
DWORD
SidNameCache::RecordMgr::AllocBlocks(
DWORD cBlocksReqd
)
{
DBGASSERT((DATA_FILE_MAPPED));
DWORD iBlock = m_pFileHdr->iFirstFree;
DBGPRINT((DM_SIDCACHE, DL_MID,
TEXT("SIDCACHE - AllocBlocks: Allocate %d blocks"),
cBlocksReqd));
while(iBlock < m_pFileHdr->cBlocks)
{
DBGPRINT((DM_SIDCACHE, DL_MID,
TEXT(" Start scan at block %d"), iBlock));
//
// Look for cBlocksReqd consecutive free blocks.
//
for (UINT j = 0; j < cBlocksReqd && (iBlock + j) < m_pFileHdr->cBlocks ; j++)
{
DBGPRINT((DM_SIDCACHE, DL_MID, TEXT(" Checking %d"), iBlock + j));
if (IsBlockUsed(iBlock + j))
{
//
// This one's used. Start searching again.
//
DBGPRINT((DM_SIDCACHE, DL_MID, TEXT(" %d is used"), iBlock + j));
break;
}
#if DBG
//
// If a block is marked "unused", it should contain all 0xCC.
//
DAT_BASED(BYTE) *pb = DAT_BASED_CAST(BYTE, BlockAddress(iBlock + j));
for (UINT k = 0; k < sizeof(BLOCK); k++)
{
DBGASSERT((RECORD_UNUSED_BYTE == *(pb + k)));
}
#endif
}
DBGPRINT((DM_SIDCACHE, DL_MID, TEXT(" Scan complete. %d blocks checked"), j));
if (j == cBlocksReqd)
{
//
// Found a sufficient range of free blocks.
// Mark the blocks as allocated in the allocation bitmap.
//
for (UINT i = 0; i < cBlocksReqd; i++)
MarkBlockUsed(iBlock + i);
if (iBlock == m_pFileHdr->iFirstFree)
{
//
// Now scan to find the next free block.
// We'll save it's location to help with future free-block searches.
//
for (m_pFileHdr->iFirstFree = iBlock + cBlocksReqd;
m_pFileHdr->iFirstFree < m_pFileHdr->cBlocks && IsBlockUsed(m_pFileHdr->iFirstFree);
m_pFileHdr->iFirstFree++)
{
DBGPRINT((DM_SIDCACHE, DL_MID,
TEXT("SIDCACHE - Advancing first free %d"),
m_pFileHdr->iFirstFree));
NULL;
}
}
DBGPRINT((DM_SIDCACHE, DL_MID, TEXT("SIDCACHE - Found free block range at %d"), iBlock));
return iBlock;
}
iBlock += (j + 1); // Continue search.
}
DBGPRINT((DM_SIDCACHE, DL_MID, TEXT("SIDCACHE - No blocks available")));
return (DWORD)-1; // No blocks available of sufficient size.
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::RecordExpired
Description: Determine if a given record has expired. A record has
"expired" if it's expiration date is prior to "today".
Arguments:
iBlock - 0-based index of first block in record.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
BOOL
SidNameCache::RecordMgr::RecordExpired(
DWORD iBlock
)
{
DBGASSERT((ValidBlockNumber(iBlock)));
DAT_BASED(RECORD_HDR) *pRec = DAT_BASED_CAST(RECORD_HDR, BlockAddress(iBlock));
DBGASSERT((SidNameCache::IsQuadAligned(pRec)));
DBGASSERT((RECORD_SIGNATURE == pRec->dwSignature));
SYSTEMTIME SysNow;
FILETIME FileNow;
ULARGE_INTEGER uliFileNow;
ULARGE_INTEGER uliBirthday;
uliBirthday.LowPart = pRec->Birthday.dwLowDateTime;
uliBirthday.HighPart = pRec->Birthday.dwHighDateTime;
GetSystemTime(&SysNow);
SystemTimeToFileTime(&SysNow, &FileNow);
uliFileNow.LowPart = FileNow.dwLowDateTime;
uliFileNow.HighPart = FileNow.dwHighDateTime;
DWORD cDaysVariation = 0;
//
// Avoid div-by-zero.
//
if (0 < m_cDaysRecLifeRange)
cDaysVariation = SysNow.wMilliseconds % m_cDaysRecLifeRange;
//
// Add time specified in registry to the record's birthday.
// 864,000,000,000L is the number of 100 nanosecond periods in a day
// which is the unit the FILETIME structure is based on.
//
uliBirthday.QuadPart += ((UINT64)864000000000L *
(UINT64)(m_cDaysRecLifeMin + cDaysVariation));
//
// If it's still less than "now", the record is considered good.
//
return uliFileNow.QuadPart > uliBirthday.QuadPart;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::Store
Description: Stores a record in the data file.
Arguments:
pSid - Address of user's SID.
pszContainer - Address of user's account container name string.
pszLogonName - Address of user's logon name string.
pszDisplayName - Address of user's display name string.
Returns: Index of the first block used for the record.
(DWORD)-1 if the block couldn't be stored. Could mean out-of-disk
space.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
DWORD
SidNameCache::RecordMgr::Store(
PSID pSid,
LPCTSTR pszContainer,
LPCTSTR pszLogonName,
LPCTSTR pszDisplayName
)
{
DWORD cbSid = 0;
DWORD cbContainer = 0;
DWORD cbLogonName = 0;
DWORD cbDisplayName = 0;
DWORD cbRequired = BytesRequiredForRecord(
pSid,
&cbSid,
pszContainer,
&cbContainer,
pszLogonName,
&cbLogonName,
pszDisplayName,
&cbDisplayName);
DWORD cBlocksRequired = BlocksRequired(cbRequired);
DBGPRINT((DM_SIDCACHE, DL_MID,
TEXT("SIDCACHE - Store: %s (%s) in \"%s\" %d bytes, %d blocks"),
pszDisplayName, pszLogonName, pszContainer, cbRequired, cBlocksRequired));
//
// Try to allocate the required blocks.
//
DWORD iBlock = AllocBlocks(cBlocksRequired);
if ((DWORD)-1 == iBlock)
{
//
// Couldn't allocate blocks. Extend the data file.
//
GrowDataFile(DATA_FILE_GROW_BLOCKS);
iBlock = AllocBlocks(cBlocksRequired);
}
if ((DWORD)-1 != iBlock)
{
//
// Got the required number of blocks.
//
DBGASSERT((ValidBlockNumber(iBlock)));
PBLOCK pBlock = BlockAddress(iBlock);
DAT_BASED(RECORD_HDR) *pRec = DAT_BASED_CAST(RECORD_HDR, pBlock);
DAT_BASED(BYTE) *pbRec = DAT_BASED_CAST(BYTE, pBlock);
//
// Fill in the record header.
// Includes storing the item offset values from the start of the record.
// Storing these values in the record hdr will help with data retrieval.
//
pRec->dwSignature = RECORD_SIGNATURE;
pRec->cBlocks = cBlocksRequired;
pRec->cbOfsSid = sizeof(RECORD_HDR);
pRec->cbOfsContainer = pRec->cbOfsSid + cbSid;
WordAlign(&pRec->cbOfsContainer);
pRec->cbOfsLogonName = pRec->cbOfsContainer + cbContainer;
pRec->cbOfsDisplayName = pRec->cbOfsLogonName + cbLogonName;;
//
// Record the "birthday" so we can age the record.
//
SYSTEMTIME SysNow;
GetSystemTime(&SysNow);
SystemTimeToFileTime(&SysNow, &pRec->Birthday);
//
// Store the record's data.
// Use ACTUAL length values for memory transfers.
//
CopySid(cbSid, (PSID)(pbRec + pRec->cbOfsSid), pSid);
lstrcpy((LPTSTR)(pbRec + pRec->cbOfsContainer), pszContainer);
lstrcpy((LPTSTR)(pbRec + pRec->cbOfsLogonName), pszLogonName);
lstrcpy((LPTSTR)(pbRec + pRec->cbOfsDisplayName), pszDisplayName);
//
// Update the file's "blocks used" count.
//
m_pFileHdr->cBlocksUsed += pRec->cBlocks;
}
return iBlock;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::Retrieve
Description: Retrieves a record in the data file.
Arguments:
iBlock - 0-based index of the starting block in the record.
ppSid [optional] - Address of SID pointer variable to receive the
address of the SID buffer. The caller is responsible for freeing
the buffer. May be NULL.
ppszContainer [optional] - Address of pointer variable to receive the
address of the container name string buffer. The caller is
responsible for freeing the buffer. May be NULL.
ppszLogonName [optional] - Address of pointer variable to receive the
address of the logon name string buffer. The caller is
responsible for freeing the buffer. May be NULL.
ppszDisplayName [optional] - Address of pointer variable to receive the
address of the display name string buffer. The caller is
responsible for freeing the buffer. May be NULL.
Returns:
NO_ERROR - Success.
ERROR_INVALID_SID (hr) - The record contains an invalid SID. Probably
a corrupt record.
Exceptions: OutOfMemory.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
HRESULT
SidNameCache::RecordMgr::Retrieve(
DWORD iBlock,
PSID *ppSid,
LPTSTR *ppszContainer,
LPTSTR *ppszLogonName,
LPTSTR *ppszDisplayName
)
{
PBLOCK pBlock = BlockAddress(iBlock);
DAT_BASED(RECORD_HDR) *pRec = DAT_BASED_CAST(RECORD_HDR, pBlock);
DAT_BASED(BYTE) *pbRec = DAT_BASED_CAST(BYTE, pBlock);
DBGASSERT((SidNameCache::IsQuadAligned(pRec)));
DBGASSERT((RECORD_SIGNATURE == pRec->dwSignature));
if (NULL != ppSid)
{
PSID pSid = (PSID)(pbRec + pRec->cbOfsSid);
if (IsValidSid(pSid))
{
*ppSid = SidDup(pSid);
}
else
return HRESULT_FROM_WIN32(ERROR_INVALID_SID);
}
if (NULL != ppszContainer)
{
*ppszContainer = StringDup((LPTSTR)(pbRec + pRec->cbOfsContainer));
}
if (NULL != ppszLogonName)
{
*ppszLogonName = StringDup((LPTSTR)(pbRec + pRec->cbOfsLogonName));
}
if (NULL != ppszDisplayName)
{
*ppszDisplayName = StringDup((LPTSTR)(pbRec + pRec->cbOfsDisplayName));
}
return NO_ERROR;
}
///////////////////////////////////////////////////////////////////////////////
/* Function: SidNameCache::RecordMgr::Dump
Description: Dumps the contents of the data file to the debugger output.
Arguments: None.
Returns: Nothing.
Revision History:
Date Description Programmer
-------- --------------------------------------------------- ----------
09/21/96 Initial creation. BrianAu
*/
///////////////////////////////////////////////////////////////////////////////
#if DBG
VOID
SidNameCache::RecordMgr::Dump(
VOID
)
{
UINT i, j;
DBGASSERT((DATA_FILE_MAPPED));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("Dumping SidNameCache RecordMgr at 0x%p"), this));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" Base...............: 0x%p"), g_pbMappedDataFile));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" m_pFileHdr.........: 0x%p"), m_pFileHdr));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" dwSignature......: 0x%08X"), (DWORD)m_pFileHdr->dwSignature));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" dwVersion........: 0x%08X"), (DWORD)m_pFileHdr->dwVersion));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" cBlocks..........: %d"), (DWORD)m_pFileHdr->cBlocks));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" cBlocksUsed......: %d"), (DWORD)m_pFileHdr->cBlocksUsed));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" cMapElements.....: %d"), (DWORD)m_pFileHdr->cMapElements));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" iFirstFree.......: %d"), (DWORD)m_pFileHdr->iFirstFree));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" pdwMap...........: 0x%p"), m_pFileHdr->pdwMap));
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT(" pBlocks..........: 0x%p"), m_pFileHdr->pBlocks));
PBLOCK pBlock = m_pFileHdr->pBlocks;
for (i = 0; i < m_pFileHdr->cBlocks; i++)
{
DAT_BASED(BYTE) *pb = DAT_BASED_CAST(BYTE, pBlock);
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("BLOCK %d --------------------"), i));
for (UINT row = 0; row < 2; row++)
{
TCHAR szHex[MAX_PATH];
TCHAR szAscii[MAX_PATH];
LPTSTR pszHex = szHex;
pb = DAT_BASED_CAST(BYTE, pBlock) + (row * (sizeof(BLOCK)/2));
for (j = 0; j < 16; j++)
{
wsprintf(pszHex, TEXT("%02X "), *pb);
pb++;
pszHex += 2;
}
LPTSTR pszAscii = szAscii;
pb = DAT_BASED_CAST(BYTE, pBlock) + (row * (sizeof(BLOCK)/2));
for (j = 0; j < 16; j++)
{
wsprintf(pszAscii, TEXT("%c"), *pb > 31 ? *pb : TEXT('.'));
pb++;
pszAscii++;
}
DBGPRINT((DM_SIDCACHE, DL_LOW, TEXT("%s %s"), szHex, szAscii));
}
pBlock++;
}
}
#endif // DEBUG