You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1264 lines
30 KiB
1264 lines
30 KiB
/*
|
|
* string.c - String table ADT module.
|
|
*/
|
|
|
|
/*
|
|
|
|
The string table ADT implemented in this module is set up as a hash table
|
|
with HASH_TABLE_SIZE buckets. A hash function is calculated for each string to
|
|
determine its bucket. Multiple strings in a single bucket are stored in a
|
|
linked list. The string hash table allows us to keep only one copy of a string
|
|
that is used multiple times. Strings are allocated in the heap by
|
|
AllocateMemory().
|
|
|
|
Every string has a list node structure associated with it. A string is
|
|
accessed through its associated list node. Each hash bucket is a list of
|
|
string nodes. A handle to a string table is a pointer to the base of the
|
|
string table's array of hash buckets. String tables are allocated in the heap
|
|
by AllocateMemory(). Each element in an array of hash buckets is a handle to a
|
|
list of strings in the hash bucket. A handle to a string is a handle to a node
|
|
in the string's hash bucket's list.
|
|
|
|
Hash table ADTs are predicated on the idea that hash buckets will typically
|
|
be shallow, so the search of a hash bucket will not take horrendously long.
|
|
The data objects in hash buckets should be stored in sorted order to reduce
|
|
search time. If hash buckets get too deep, increase the hash table size.
|
|
Ideally, the hash table should be implemented as a container class that hashes
|
|
arbitrary data objects given an initial hash table size, the size of the
|
|
objects to be hashed, a hash function, and a data object comparison function.
|
|
|
|
Currently the hash table ADT is restricted to strings, the strings in each
|
|
hash bucket are stored in sorted order, and hash buckets are binary searched.
|
|
|
|
*/
|
|
|
|
|
|
/* Headers
|
|
**********/
|
|
|
|
#include "project.h"
|
|
#pragma hdrstop
|
|
|
|
|
|
/* Types
|
|
********/
|
|
|
|
/* string table */
|
|
|
|
typedef struct _stringtable
|
|
{
|
|
/* number of hash buckets in string table */
|
|
|
|
HASHBUCKETCOUNT hbc;
|
|
|
|
/* pointer to array of hash buckets (HLISTs) */
|
|
|
|
PHLIST phlistHashBuckets;
|
|
}
|
|
STRINGTABLE;
|
|
DECLARE_STANDARD_TYPES(STRINGTABLE);
|
|
|
|
/* string heap structure */
|
|
|
|
typedef struct _string
|
|
{
|
|
/* lock count of string */
|
|
|
|
ULONG ulcLock;
|
|
|
|
/* actual string */
|
|
|
|
TCHAR string[1];
|
|
}
|
|
STRING;
|
|
DECLARE_STANDARD_TYPES(STRING);
|
|
|
|
/* string table database structure header */
|
|
|
|
typedef struct _stringtabledbheader
|
|
{
|
|
/*
|
|
* length of longest string in string table, not including null terminator
|
|
*/
|
|
|
|
DWORD dwcbMaxStringLen;
|
|
|
|
/* number of strings in string table */
|
|
|
|
LONG lcStrings;
|
|
}
|
|
STRINGTABLEDBHEADER;
|
|
DECLARE_STANDARD_TYPES(STRINGTABLEDBHEADER);
|
|
|
|
/* database string header */
|
|
|
|
typedef struct _dbstringheader
|
|
{
|
|
/* old handle to this string */
|
|
|
|
HSTRING hsOld;
|
|
}
|
|
DBSTRINGHEADER;
|
|
DECLARE_STANDARD_TYPES(DBSTRINGHEADER);
|
|
|
|
|
|
/***************************** Private Functions *****************************/
|
|
|
|
/* Module Prototypes
|
|
********************/
|
|
|
|
PRIVATE_CODE COMPARISONRESULT StringSearchCmp(PCVOID, PCVOID);
|
|
PRIVATE_CODE COMPARISONRESULT StringSortCmp(PCVOID, PCVOID);
|
|
PRIVATE_CODE BOOL UnlockString(PSTRING);
|
|
PRIVATE_CODE BOOL FreeStringWalker(PVOID, PVOID);
|
|
PRIVATE_CODE void FreeHashBucket(HLIST);
|
|
PRIVATE_CODE TWINRESULT WriteHashBucket(HCACHEDFILE, HLIST, PLONG, PDWORD);
|
|
PRIVATE_CODE TWINRESULT WriteString(HCACHEDFILE, HNODE, PSTRING, PDWORD);
|
|
PRIVATE_CODE TWINRESULT ReadString(HCACHEDFILE, HSTRINGTABLE, HHANDLETRANS, LPTSTR, DWORD);
|
|
PRIVATE_CODE TWINRESULT SlowReadString(HCACHEDFILE, LPTSTR, DWORD);
|
|
|
|
#ifdef VSTF
|
|
|
|
PRIVATE_CODE BOOL IsValidPCNEWSTRINGTABLE(PCNEWSTRINGTABLE);
|
|
PRIVATE_CODE BOOL IsValidPCSTRING(PCSTRING);
|
|
PRIVATE_CODE BOOL IsValidPCSTRINGTABLE(PCSTRINGTABLE);
|
|
|
|
#endif
|
|
|
|
|
|
/*
|
|
** StringSearchCmp()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE COMPARISONRESULT StringSearchCmp(PCVOID pcszPath, PCVOID pcstring)
|
|
{
|
|
ASSERT(IS_VALID_STRING_PTR(pcszPath, CSTR));
|
|
ASSERT(IS_VALID_STRUCT_PTR(pcstring, CSTRING));
|
|
|
|
return(MapIntToComparisonResult(lstrcmp((LPCTSTR)pcszPath,
|
|
(LPCTSTR)&(((PCSTRING)pcstring)->string))));
|
|
}
|
|
|
|
|
|
/*
|
|
** StringSortCmp()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE COMPARISONRESULT StringSortCmp(PCVOID pcstring1, PCVOID pcstring2)
|
|
{
|
|
ASSERT(IS_VALID_STRUCT_PTR(pcstring1, CSTRING));
|
|
ASSERT(IS_VALID_STRUCT_PTR(pcstring2, CSTRING));
|
|
|
|
return(MapIntToComparisonResult(lstrcmp((LPCTSTR)&(((PCSTRING)pcstring1)->string),
|
|
(LPCTSTR)&(((PCSTRING)pcstring2)->string))));
|
|
}
|
|
|
|
|
|
/*
|
|
** UnlockString()
|
|
**
|
|
** Decrements a string's lock count.
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns: void
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE BOOL UnlockString(PSTRING pstring)
|
|
{
|
|
ASSERT(IS_VALID_STRUCT_PTR(pstring, CSTRING));
|
|
|
|
/* Is the lock count going to underflow? */
|
|
|
|
if (EVAL(pstring->ulcLock > 0))
|
|
pstring->ulcLock--;
|
|
|
|
return(pstring->ulcLock > 0);
|
|
}
|
|
|
|
|
|
/*
|
|
** FreeStringWalker()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
|
|
#pragma warning(disable:4100) /* "unreferenced formal parameter" warning */
|
|
|
|
PRIVATE_CODE BOOL FreeStringWalker(PVOID pstring, PVOID pvUnused)
|
|
{
|
|
ASSERT(IS_VALID_STRUCT_PTR(pstring, CSTRING));
|
|
ASSERT(! pvUnused);
|
|
|
|
FreeMemory(pstring);
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
#pragma warning(default:4100) /* "unreferenced formal parameter" warning */
|
|
|
|
|
|
/*
|
|
** FreeHashBucket()
|
|
**
|
|
** Frees the strings in a hash bucket, and the hash bucket's string list.
|
|
**
|
|
** Arguments: hlistHashBucket - handle to hash bucket's list of strings
|
|
**
|
|
** Returns: void
|
|
**
|
|
** Side Effects: none
|
|
**
|
|
** N.b., this function ignores the lock counts of the strings in the hash
|
|
** bucket. All strings in the hash bucket are freed.
|
|
*/
|
|
PRIVATE_CODE void FreeHashBucket(HLIST hlistHashBucket)
|
|
{
|
|
ASSERT(! hlistHashBucket || IS_VALID_HANDLE(hlistHashBucket, LIST));
|
|
|
|
/* Are there any strings in this hash bucket to delete? */
|
|
|
|
if (hlistHashBucket)
|
|
{
|
|
/* Yes. Delete all strings in list. */
|
|
|
|
EVAL(WalkList(hlistHashBucket, &FreeStringWalker, NULL));
|
|
|
|
/* Delete hash bucket string list. */
|
|
|
|
DestroyList(hlistHashBucket);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
** MyGetStringLen()
|
|
**
|
|
** Retrieves the length of a string in a string table.
|
|
**
|
|
** Arguments: pcstring - pointer to string whose length is to be
|
|
** determined
|
|
**
|
|
** Returns: Length of string in bytes, not including null terminator.
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE int MyGetStringLen(PCSTRING pcstring)
|
|
{
|
|
ASSERT(IS_VALID_STRUCT_PTR(pcstring, CSTRING));
|
|
|
|
return(lstrlen(pcstring->string) * sizeof(TCHAR));
|
|
}
|
|
|
|
|
|
/*
|
|
** WriteHashBucket()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns: TWINRESULT
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE TWINRESULT WriteHashBucket(HCACHEDFILE hcf,
|
|
HLIST hlistHashBucket,
|
|
PLONG plcStrings,
|
|
PDWORD pdwcbMaxStringLen)
|
|
{
|
|
TWINRESULT tr = TR_SUCCESS;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
|
|
ASSERT(! hlistHashBucket || IS_VALID_HANDLE(hlistHashBucket, LIST));
|
|
ASSERT(IS_VALID_WRITE_PTR(plcStrings, LONG));
|
|
ASSERT(IS_VALID_WRITE_PTR(pdwcbMaxStringLen, DWORD));
|
|
|
|
/* Any strings in this hash bucket? */
|
|
|
|
*plcStrings = 0;
|
|
*pdwcbMaxStringLen = 0;
|
|
|
|
if (hlistHashBucket)
|
|
{
|
|
BOOL bContinue;
|
|
HNODE hnode;
|
|
|
|
/* Yes. Walk hash bucket, saving each string. */
|
|
|
|
for (bContinue = GetFirstNode(hlistHashBucket, &hnode);
|
|
bContinue;
|
|
bContinue = GetNextNode(hnode, &hnode))
|
|
{
|
|
PSTRING pstring;
|
|
|
|
pstring = (PSTRING)GetNodeData(hnode);
|
|
|
|
ASSERT(IS_VALID_STRUCT_PTR(pstring, CSTRING));
|
|
|
|
/*
|
|
* As a sanity check, don't save any string with a lock count of 0. A
|
|
* 0 lock count implies that the string has not been referenced since
|
|
* it was restored from the database, or something is broken.
|
|
*/
|
|
|
|
if (pstring->ulcLock > 0)
|
|
{
|
|
DWORD dwcbStringLen;
|
|
|
|
tr = WriteString(hcf, hnode, pstring, &dwcbStringLen);
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
if (dwcbStringLen > *pdwcbMaxStringLen)
|
|
*pdwcbMaxStringLen = dwcbStringLen;
|
|
|
|
ASSERT(*plcStrings < LONG_MAX);
|
|
(*plcStrings)++;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
ERROR_OUT((TEXT("WriteHashBucket(): String \"%s\" has 0 lock count and will not be saved."),
|
|
pstring->string));
|
|
}
|
|
}
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** WriteString()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns: TWINRESULT
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE TWINRESULT WriteString(HCACHEDFILE hcf, HNODE hnodeOld,
|
|
PSTRING pstring, PDWORD pdwcbStringLen)
|
|
{
|
|
TWINRESULT tr = TR_BRIEFCASE_WRITE_FAILED;
|
|
DBSTRINGHEADER dbsh;
|
|
|
|
/* (+ 1) for null terminator. */
|
|
|
|
ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
|
|
ASSERT(IS_VALID_HANDLE(hnodeOld, NODE));
|
|
ASSERT(IS_VALID_STRUCT_PTR(pstring, CSTRING));
|
|
ASSERT(IS_VALID_READ_BUFFER_PTR(pstring, STRING, sizeof(STRING) + MyGetStringLen(pstring) + sizeof(TCHAR) - sizeof(pstring->string)));
|
|
ASSERT(IS_VALID_WRITE_PTR(pdwcbStringLen, DWORD));
|
|
|
|
/* Create string header. */
|
|
|
|
dbsh.hsOld = (HSTRING)hnodeOld;
|
|
|
|
/* Save string header and string. */
|
|
|
|
if (WriteToCachedFile(hcf, (PCVOID)&dbsh, sizeof(dbsh), NULL))
|
|
{
|
|
LPSTR pszAnsi;
|
|
|
|
/* (+ 1) for null terminator. */
|
|
|
|
*pdwcbStringLen = MyGetStringLen(pstring) + SIZEOF(TCHAR);
|
|
|
|
// If its unicode, convert the string to ansi before writing it out
|
|
|
|
#ifdef UNICODE
|
|
{
|
|
pszAnsi = LocalAlloc(LPTR, *pdwcbStringLen);
|
|
if (NULL == pszAnsi)
|
|
{
|
|
return tr;
|
|
}
|
|
WideCharToMultiByte(CP_ACP, 0, pstring->string, -1, pszAnsi, *pdwcbStringLen, NULL, NULL);
|
|
|
|
// We should always have a string at this point that can be converted losslessly
|
|
|
|
#if (defined(DEBUG) || defined(DBG)) && defined(UNICODE)
|
|
{
|
|
WCHAR szUnicode[MAX_PATH*2];
|
|
MultiByteToWideChar(CP_ACP, 0, pszAnsi, -1, szUnicode, ARRAYSIZE(szUnicode));
|
|
ASSERT(0 == lstrcmp(szUnicode, pstring->string));
|
|
}
|
|
#endif
|
|
|
|
if (WriteToCachedFile(hcf, (PCVOID) pszAnsi, lstrlenA(pszAnsi) + 1, NULL))
|
|
tr = TR_SUCCESS;
|
|
|
|
LocalFree(pszAnsi);
|
|
}
|
|
#else
|
|
|
|
if (WriteToCachedFile(hcf, (PCVOID)&(pstring->string), (UINT)*pdwcbStringLen, NULL))
|
|
tr = TR_SUCCESS;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** ReadString()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns: TWINRESULT
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE TWINRESULT ReadString(HCACHEDFILE hcf, HSTRINGTABLE hst,
|
|
HHANDLETRANS hht, LPTSTR pszStringBuf,
|
|
DWORD dwcbStringBufLen)
|
|
{
|
|
TWINRESULT tr;
|
|
DBSTRINGHEADER dbsh;
|
|
DWORD dwcbRead;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
|
|
ASSERT(IS_VALID_HANDLE(hst, STRINGTABLE));
|
|
ASSERT(IS_VALID_HANDLE(hht, HANDLETRANS));
|
|
ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszStringBuf, STR, (UINT)dwcbStringBufLen));
|
|
|
|
if (ReadFromCachedFile(hcf, &dbsh, sizeof(dbsh), &dwcbRead) &&
|
|
dwcbRead == sizeof(dbsh))
|
|
{
|
|
tr = SlowReadString(hcf, pszStringBuf, dwcbStringBufLen);
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
HSTRING hsNew;
|
|
|
|
if (AddString(pszStringBuf, hst, GetHashBucketIndex, &hsNew))
|
|
{
|
|
/*
|
|
* We must undo the LockString() performed by AddString() to
|
|
* maintain the correct string lock count. N.b., the lock count of
|
|
* a string may be > 0 even after unlocking since the client may
|
|
* already have added the string to the given string table.
|
|
*/
|
|
|
|
UnlockString((PSTRING)GetNodeData((HNODE)hsNew));
|
|
|
|
if (! AddHandleToHandleTranslator(hht, (HGENERIC)(dbsh.hsOld), (HGENERIC)hsNew))
|
|
{
|
|
DeleteNode((HNODE)hsNew);
|
|
|
|
tr = TR_CORRUPT_BRIEFCASE;
|
|
}
|
|
}
|
|
else
|
|
tr = TR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
else
|
|
tr = TR_CORRUPT_BRIEFCASE;
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** SlowReadString()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE TWINRESULT SlowReadString(HCACHEDFILE hcf, LPTSTR pszStringBuf,
|
|
DWORD dwcbStringBufLen)
|
|
{
|
|
TWINRESULT tr = TR_CORRUPT_BRIEFCASE;
|
|
LPTSTR pszStringBufEnd;
|
|
DWORD dwcbRead;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
|
|
ASSERT(IS_VALID_WRITE_BUFFER_PTR(pszStringBuf, STR, (UINT)dwcbStringBufLen));
|
|
|
|
pszStringBufEnd = pszStringBuf + dwcbStringBufLen;
|
|
|
|
// The database strings are always written ANSI, so if we are running unicode,
|
|
// we need to convert as we go
|
|
|
|
#ifdef UNICODE
|
|
{
|
|
LPSTR pszAnsiEnd;
|
|
LPSTR pszAnsiStart;
|
|
LPSTR pszAnsi = LocalAlloc(LPTR, dwcbStringBufLen);
|
|
pszAnsiStart = pszAnsi;
|
|
pszAnsiEnd = pszAnsi + dwcbStringBufLen;
|
|
|
|
if (NULL == pszAnsi)
|
|
{
|
|
return tr;
|
|
}
|
|
|
|
while (pszAnsi < pszAnsiEnd &&
|
|
ReadFromCachedFile(hcf, pszAnsi, sizeof(*pszAnsi), &dwcbRead) &&
|
|
dwcbRead == sizeof(*pszAnsi))
|
|
{
|
|
if (*pszAnsi)
|
|
pszAnsi++;
|
|
else
|
|
{
|
|
tr = TR_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
MultiByteToWideChar(CP_ACP, 0, pszAnsiStart, -1, pszStringBuf, dwcbStringBufLen / sizeof(TCHAR));
|
|
}
|
|
|
|
LocalFree(pszAnsiStart);
|
|
}
|
|
#else
|
|
|
|
while (pszStringBuf < pszStringBufEnd &&
|
|
ReadFromCachedFile(hcf, pszStringBuf, sizeof(*pszStringBuf), &dwcbRead) &&
|
|
dwcbRead == sizeof(*pszStringBuf))
|
|
{
|
|
if (*pszStringBuf)
|
|
pszStringBuf++;
|
|
else
|
|
{
|
|
tr = TR_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
#ifdef VSTF
|
|
|
|
/*
|
|
** IsValidPCNEWSTRINGTABLE()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE BOOL IsValidPCNEWSTRINGTABLE(PCNEWSTRINGTABLE pcnst)
|
|
{
|
|
BOOL bResult;
|
|
|
|
if (IS_VALID_READ_PTR(pcnst, CNEWSTRINGTABLE) &&
|
|
EVAL(pcnst->hbc > 0))
|
|
bResult = TRUE;
|
|
else
|
|
bResult = FALSE;
|
|
|
|
return(bResult);
|
|
}
|
|
|
|
|
|
/*
|
|
** IsValidPCSTRING()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE BOOL IsValidPCSTRING(PCSTRING pcs)
|
|
{
|
|
BOOL bResult;
|
|
|
|
if (IS_VALID_READ_PTR(pcs, CSTRING) &&
|
|
IS_VALID_STRING_PTR(pcs->string, CSTR))
|
|
bResult = TRUE;
|
|
else
|
|
bResult = FALSE;
|
|
|
|
return(bResult);
|
|
}
|
|
|
|
|
|
/*
|
|
** IsValidStringWalker()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
|
|
#pragma warning(disable:4100) /* "unreferenced formal parameter" warning */
|
|
|
|
PRIVATE_CODE BOOL IsValidStringWalker(PVOID pstring, PVOID pvUnused)
|
|
{
|
|
ASSERT(! pvUnused);
|
|
|
|
return(IS_VALID_STRUCT_PTR(pstring, CSTRING));
|
|
}
|
|
|
|
#pragma warning(default:4100) /* "unreferenced formal parameter" warning */
|
|
|
|
|
|
/*
|
|
** IsValidPCSTRINGTABLE()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PRIVATE_CODE BOOL IsValidPCSTRINGTABLE(PCSTRINGTABLE pcst)
|
|
{
|
|
BOOL bResult = FALSE;
|
|
|
|
if (IS_VALID_READ_PTR(pcst, CSTRINGTABLE) &&
|
|
EVAL(pcst->hbc > 0) &&
|
|
IS_VALID_READ_BUFFER_PTR(pcst->phlistHashBuckets, HLIST, pcst->hbc * sizeof((pcst->phlistHashBuckets)[0])))
|
|
{
|
|
HASHBUCKETCOUNT hbc;
|
|
|
|
for (hbc = 0; hbc < pcst->hbc; hbc++)
|
|
{
|
|
HLIST hlistHashBucket;
|
|
|
|
hlistHashBucket = (pcst->phlistHashBuckets)[hbc];
|
|
|
|
if (hlistHashBucket)
|
|
{
|
|
if (! IS_VALID_HANDLE(hlistHashBucket, LIST) ||
|
|
! WalkList(hlistHashBucket, &IsValidStringWalker, NULL))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hbc == pcst->hbc)
|
|
bResult = TRUE;
|
|
}
|
|
|
|
return(bResult);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
/****************************** Public Functions *****************************/
|
|
|
|
/*
|
|
** CreateStringTable()
|
|
**
|
|
** Creates a new string table.
|
|
**
|
|
** Arguments: pcnszt - pointer to NEWSTRINGTABLE descibing string table to
|
|
** be created
|
|
**
|
|
** Returns: Handle to new string table if successful, or NULL if
|
|
** unsuccessful.
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE BOOL CreateStringTable(PCNEWSTRINGTABLE pcnszt,
|
|
PHSTRINGTABLE phst)
|
|
{
|
|
PSTRINGTABLE pst;
|
|
|
|
ASSERT(IS_VALID_STRUCT_PTR(pcnszt, CNEWSTRINGTABLE));
|
|
ASSERT(IS_VALID_WRITE_PTR(phst, HSTRINGTABLE));
|
|
|
|
/* Try to allocate new string table structure. */
|
|
|
|
*phst = NULL;
|
|
|
|
if (AllocateMemory(sizeof(*pst), &pst))
|
|
{
|
|
PHLIST phlistHashBuckets;
|
|
|
|
/* Try to allocate hash bucket array. */
|
|
|
|
#ifdef DBLCHECK
|
|
ASSERT((double)(pcnszt->hbc) * (double)(sizeof(*phlistHashBuckets)) <= (double)SIZE_T_MAX);
|
|
#endif
|
|
|
|
if (AllocateMemory(pcnszt->hbc * sizeof(*phlistHashBuckets), (PVOID *)(&phlistHashBuckets)))
|
|
{
|
|
HASHBUCKETCOUNT bc;
|
|
|
|
/* Successs! Initialize STRINGTABLE fields. */
|
|
|
|
pst->phlistHashBuckets = phlistHashBuckets;
|
|
pst->hbc = pcnszt->hbc;
|
|
|
|
/* Initialize all hash buckets to NULL. */
|
|
|
|
for (bc = 0; bc < pcnszt->hbc; bc++)
|
|
phlistHashBuckets[bc] = NULL;
|
|
|
|
*phst = (HSTRINGTABLE)pst;
|
|
|
|
ASSERT(IS_VALID_HANDLE(*phst, STRINGTABLE));
|
|
}
|
|
else
|
|
/* Free string table structure. */
|
|
FreeMemory(pst);
|
|
}
|
|
|
|
return(*phst != NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
** DestroyStringTable()
|
|
**
|
|
** Destroys a string table.
|
|
**
|
|
** Arguments: hst - handle to string table to be destroyed
|
|
**
|
|
** Returns: void
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE void DestroyStringTable(HSTRINGTABLE hst)
|
|
{
|
|
HASHBUCKETCOUNT bc;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hst, STRINGTABLE));
|
|
|
|
/* Traverse array of hash bucket heads, freeing hash bucket strings. */
|
|
|
|
for (bc = 0; bc < ((PSTRINGTABLE)hst)->hbc; bc++)
|
|
FreeHashBucket(((PSTRINGTABLE)hst)->phlistHashBuckets[bc]);
|
|
|
|
/* Free array of hash buckets. */
|
|
|
|
FreeMemory(((PSTRINGTABLE)hst)->phlistHashBuckets);
|
|
|
|
/* Free string table structure. */
|
|
|
|
FreeMemory((PSTRINGTABLE)hst);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
** AddString()
|
|
**
|
|
** Adds a string to a string table.
|
|
**
|
|
** Arguments: pcsz - pointer to string to be added
|
|
** hst - handle to string table that string is to be added to
|
|
**
|
|
** Returns: Handle to new string if successful, or NULL if unsuccessful.
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE BOOL AddString(LPCTSTR pcsz, HSTRINGTABLE hst,
|
|
STRINGTABLEHASHFUNC pfnHashFunc, PHSTRING phs)
|
|
{
|
|
BOOL bResult;
|
|
HASHBUCKETCOUNT hbcNew;
|
|
BOOL bFound;
|
|
HNODE hnode;
|
|
PHLIST phlistHashBucket;
|
|
|
|
ASSERT(IS_VALID_STRING_PTR(pcsz, CSTR));
|
|
ASSERT(IS_VALID_HANDLE(hst, STRINGTABLE));
|
|
ASSERT(IS_VALID_CODE_PTR(pfnHashFunc, STRINGTABLEHASHFUNC));
|
|
ASSERT(IS_VALID_WRITE_PTR(phs, HSTRING));
|
|
|
|
/* Find appropriate hash bucket. */
|
|
|
|
hbcNew = pfnHashFunc(pcsz, ((PSTRINGTABLE)hst)->hbc);
|
|
|
|
ASSERT(hbcNew < ((PSTRINGTABLE)hst)->hbc);
|
|
|
|
phlistHashBucket = &(((PSTRINGTABLE)hst)->phlistHashBuckets[hbcNew]);
|
|
|
|
if (*phlistHashBucket)
|
|
{
|
|
/* Search the hash bucket for the string. */
|
|
|
|
bFound = SearchSortedList(*phlistHashBucket, &StringSearchCmp, pcsz,
|
|
&hnode);
|
|
bResult = TRUE;
|
|
}
|
|
else
|
|
{
|
|
NEWLIST nl;
|
|
|
|
/* Create a string list for this hash bucket. */
|
|
|
|
bFound = FALSE;
|
|
|
|
nl.dwFlags = NL_FL_SORTED_ADD;
|
|
|
|
bResult = CreateList(&nl, phlistHashBucket);
|
|
}
|
|
|
|
/* Do we have a hash bucket for the string? */
|
|
|
|
if (bResult)
|
|
{
|
|
/* Yes. Is the string already in the hash bucket? */
|
|
|
|
if (bFound)
|
|
{
|
|
/* Yes. */
|
|
|
|
LockString((HSTRING)hnode);
|
|
*phs = (HSTRING)hnode;
|
|
}
|
|
else
|
|
{
|
|
/* No. Create it. */
|
|
|
|
PSTRING pstringNew;
|
|
|
|
/* (+ 1) for null terminator. */
|
|
|
|
bResult = AllocateMemory(sizeof(*pstringNew) - sizeof(pstringNew->string)
|
|
+ (lstrlen(pcsz) + 1) * sizeof(TCHAR), &pstringNew);
|
|
|
|
if (bResult)
|
|
{
|
|
HNODE hnodeNew;
|
|
|
|
/* Set up STRING fields. */
|
|
|
|
pstringNew->ulcLock = 1;
|
|
lstrcpy(pstringNew->string, pcsz); // dynamically allocated above
|
|
|
|
/* What's up with this string, Doc? */
|
|
|
|
bResult = AddNode(*phlistHashBucket, StringSortCmp, pstringNew, &hnodeNew);
|
|
|
|
/* Was the new string added to the hash bucket successfully? */
|
|
|
|
if (bResult)
|
|
/* Yes. */
|
|
*phs = (HSTRING)hnodeNew;
|
|
else
|
|
/* No. */
|
|
FreeMemory(pstringNew);
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(! bResult ||
|
|
IS_VALID_HANDLE(*phs, STRING));
|
|
|
|
return(bResult);
|
|
}
|
|
|
|
|
|
/*
|
|
** DeleteString()
|
|
**
|
|
** Decrements a string's lock count. If the lock count goes to 0, the string
|
|
** is deleted from its string table.
|
|
**
|
|
** Arguments: hs - handle to the string to be deleted
|
|
**
|
|
** Returns: void
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE void DeleteString(HSTRING hs)
|
|
{
|
|
PSTRING pstring;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hs, STRING));
|
|
|
|
pstring = (PSTRING)GetNodeData((HNODE)hs);
|
|
|
|
/* Delete string completely? */
|
|
|
|
if (! UnlockString(pstring))
|
|
{
|
|
/* Yes. Remove the string node from the hash bucket's list. */
|
|
|
|
DeleteNode((HNODE)hs);
|
|
|
|
FreeMemory(pstring);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
** LockString()
|
|
**
|
|
** Increments a string's lock count.
|
|
**
|
|
** Arguments: hs - handle to string whose lock count is to be incremented
|
|
**
|
|
** Returns: void
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE void LockString(HSTRING hs)
|
|
{
|
|
PSTRING pstring;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hs, STRING));
|
|
|
|
/* Increment lock count. */
|
|
|
|
pstring = (PSTRING)GetNodeData((HNODE)hs);
|
|
|
|
ASSERT(pstring->ulcLock < ULONG_MAX);
|
|
pstring->ulcLock++;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
** CompareStrings()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE COMPARISONRESULT CompareStringsI(HSTRING hs1, HSTRING hs2)
|
|
{
|
|
ASSERT(IS_VALID_HANDLE(hs1, STRING));
|
|
ASSERT(IS_VALID_HANDLE(hs2, STRING));
|
|
|
|
/* This comparison works across string tables. */
|
|
|
|
return(MapIntToComparisonResult(lstrcmpi(((PCSTRING)GetNodeData((HNODE)hs1))->string,
|
|
((PCSTRING)GetNodeData((HNODE)hs2))->string)));
|
|
}
|
|
|
|
|
|
/*
|
|
** GetString()
|
|
**
|
|
** Retrieves a pointer to a string in a string table.
|
|
**
|
|
** Arguments: hs - handle to the string to be retrieved
|
|
**
|
|
** Returns: Pointer to string.
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE LPCTSTR GetString(HSTRING hs)
|
|
{
|
|
PSTRING pstring;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hs, STRING));
|
|
|
|
pstring = (PSTRING)GetNodeData((HNODE)hs);
|
|
|
|
return((LPCTSTR)&(pstring->string));
|
|
}
|
|
|
|
|
|
/*
|
|
** WriteStringTable()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns: TWINRESULT
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE TWINRESULT WriteStringTable(HCACHEDFILE hcf, HSTRINGTABLE hst)
|
|
{
|
|
TWINRESULT tr = TR_BRIEFCASE_WRITE_FAILED;
|
|
DWORD dwcbStringTableDBHeaderOffset;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
|
|
ASSERT(IS_VALID_HANDLE(hst, STRINGTABLE));
|
|
|
|
/* Save initial file poisition. */
|
|
|
|
dwcbStringTableDBHeaderOffset = GetCachedFilePointerPosition(hcf);
|
|
|
|
if (dwcbStringTableDBHeaderOffset != INVALID_SEEK_POSITION)
|
|
{
|
|
STRINGTABLEDBHEADER stdbh;
|
|
|
|
/* Leave space for the string table header. */
|
|
|
|
ZeroMemory(&stdbh, sizeof(stdbh));
|
|
|
|
if (WriteToCachedFile(hcf, (PCVOID)&stdbh, sizeof(stdbh), NULL))
|
|
{
|
|
HASHBUCKETCOUNT hbc;
|
|
|
|
/* Save strings in each hash bucket. */
|
|
|
|
stdbh.dwcbMaxStringLen = 0;
|
|
stdbh.lcStrings = 0;
|
|
|
|
tr = TR_SUCCESS;
|
|
|
|
for (hbc = 0; hbc < ((PSTRINGTABLE)hst)->hbc; hbc++)
|
|
{
|
|
LONG lcStringsInHashBucket;
|
|
DWORD dwcbStringLen;
|
|
|
|
tr = WriteHashBucket(hcf,
|
|
(((PSTRINGTABLE)hst)->phlistHashBuckets)[hbc],
|
|
&lcStringsInHashBucket, &dwcbStringLen);
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
/* Watch out for overflow. */
|
|
|
|
ASSERT(stdbh.lcStrings <= LONG_MAX - lcStringsInHashBucket);
|
|
|
|
stdbh.lcStrings += lcStringsInHashBucket;
|
|
|
|
if (dwcbStringLen > stdbh.dwcbMaxStringLen)
|
|
stdbh.dwcbMaxStringLen = dwcbStringLen;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
/* Save string table header. */
|
|
|
|
// The on-disk dwCBMaxString len always refers to ANSI chars,
|
|
// whereas in memory it is for the TCHAR type, we adjust it
|
|
// around the save
|
|
|
|
stdbh.dwcbMaxStringLen /= sizeof(TCHAR);
|
|
|
|
tr = WriteDBSegmentHeader(hcf, dwcbStringTableDBHeaderOffset,
|
|
&stdbh, sizeof(stdbh));
|
|
|
|
stdbh.dwcbMaxStringLen *= sizeof(TCHAR);
|
|
|
|
TRACE_OUT((TEXT("WriteStringTable(): Wrote %ld strings."),
|
|
stdbh.lcStrings));
|
|
}
|
|
}
|
|
}
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
/*
|
|
** ReadStringTable()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns: TWINRESULT
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE TWINRESULT ReadStringTable(HCACHEDFILE hcf, HSTRINGTABLE hst,
|
|
PHHANDLETRANS phhtTrans)
|
|
{
|
|
TWINRESULT tr;
|
|
STRINGTABLEDBHEADER stdbh;
|
|
DWORD dwcbRead;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hcf, CACHEDFILE));
|
|
ASSERT(IS_VALID_HANDLE(hst, STRINGTABLE));
|
|
ASSERT(IS_VALID_WRITE_PTR(phhtTrans, HHANDLETRANS));
|
|
|
|
if (ReadFromCachedFile(hcf, &stdbh, sizeof(stdbh), &dwcbRead) &&
|
|
dwcbRead == sizeof(stdbh))
|
|
{
|
|
LPTSTR pszStringBuf;
|
|
|
|
// The string header will have the ANSI cb max, whereas inmemory
|
|
// we need the cb max based on the current character size
|
|
|
|
stdbh.dwcbMaxStringLen *= sizeof(TCHAR);
|
|
|
|
if (AllocateMemory(stdbh.dwcbMaxStringLen, &pszStringBuf))
|
|
{
|
|
HHANDLETRANS hht;
|
|
|
|
if (CreateHandleTranslator(stdbh.lcStrings, &hht))
|
|
{
|
|
LONG lcStrings;
|
|
|
|
tr = TR_SUCCESS;
|
|
|
|
TRACE_OUT((TEXT("ReadStringTable(): Reading %ld strings, maximum length %lu."),
|
|
stdbh.lcStrings,
|
|
stdbh.dwcbMaxStringLen));
|
|
|
|
for (lcStrings = 0;
|
|
lcStrings < stdbh.lcStrings && tr == TR_SUCCESS;
|
|
lcStrings++)
|
|
tr = ReadString(hcf, hst, hht, pszStringBuf, stdbh.dwcbMaxStringLen);
|
|
|
|
if (tr == TR_SUCCESS)
|
|
{
|
|
PrepareForHandleTranslation(hht);
|
|
*phhtTrans = hht;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hst, STRINGTABLE));
|
|
ASSERT(IS_VALID_HANDLE(*phhtTrans, HANDLETRANS));
|
|
}
|
|
else
|
|
DestroyHandleTranslator(hht);
|
|
}
|
|
else
|
|
tr = TR_OUT_OF_MEMORY;
|
|
|
|
FreeMemory(pszStringBuf);
|
|
}
|
|
else
|
|
tr = TR_OUT_OF_MEMORY;
|
|
}
|
|
else
|
|
tr = TR_CORRUPT_BRIEFCASE;
|
|
|
|
return(tr);
|
|
}
|
|
|
|
|
|
#if defined(DEBUG) || defined (VSTF)
|
|
|
|
/*
|
|
** IsValidHSTRING()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE BOOL IsValidHSTRING(HSTRING hs)
|
|
{
|
|
BOOL bResult;
|
|
|
|
if (IS_VALID_HANDLE((HNODE)hs, NODE))
|
|
bResult = IS_VALID_STRUCT_PTR((PSTRING)GetNodeData((HNODE)hs), CSTRING);
|
|
else
|
|
bResult = FALSE;
|
|
|
|
return(bResult);
|
|
}
|
|
|
|
|
|
/*
|
|
** IsValidHSTRINGTABLE()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE BOOL IsValidHSTRINGTABLE(HSTRINGTABLE hst)
|
|
{
|
|
return(IS_VALID_STRUCT_PTR((PSTRINGTABLE)hst, CSTRINGTABLE));
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
/*
|
|
** GetStringCount()
|
|
**
|
|
**
|
|
**
|
|
** Arguments:
|
|
**
|
|
** Returns:
|
|
**
|
|
** Side Effects: none
|
|
*/
|
|
PUBLIC_CODE ULONG GetStringCount(HSTRINGTABLE hst)
|
|
{
|
|
ULONG ulcStrings = 0;
|
|
HASHBUCKETCOUNT hbc;
|
|
|
|
ASSERT(IS_VALID_HANDLE(hst, STRINGTABLE));
|
|
|
|
for (hbc = 0; hbc < ((PCSTRINGTABLE)hst)->hbc; hbc++)
|
|
{
|
|
HLIST hlistHashBucket;
|
|
|
|
hlistHashBucket = (((PCSTRINGTABLE)hst)->phlistHashBuckets)[hbc];
|
|
|
|
if (hlistHashBucket)
|
|
{
|
|
ASSERT(ulcStrings <= ULONG_MAX - GetNodeCount(hlistHashBucket));
|
|
ulcStrings += GetNodeCount(hlistHashBucket);
|
|
}
|
|
}
|
|
|
|
return(ulcStrings);
|
|
}
|
|
|
|
#endif
|
|
|