2025-04-27 07:49:33 -04:00

830 lines
20 KiB
C++

/*
** WCSUTIL.CPP
** Sean Q. Nolan
**
** Web Component Team Server-Side Utilities
*/
#pragma warning(disable: 4237) // disable "bool" reserved
#include <ssobase.h>
#include "wcsutil.h"
/*--------------------------------------------------------------------------+
| Types & Constants |
+--------------------------------------------------------------------------*/
#define chLF (0xA)
#define chCR (0xD)
#define chSpace ' '
#define chTab '\t'
/*--------------------------------------------------------------------------+
| Memory Management |
+--------------------------------------------------------------------------*/
LPVOID
_MsnAlloc(DWORD cb)
{
return(::HeapAlloc(::GetProcessHeap(), 0, cb));
}
LPVOID
_MsnRealloc(LPVOID pv, DWORD cb)
{
return(::HeapReAlloc(::GetProcessHeap(), 0, pv, cb));
}
void
_MsnFree(LPVOID pv)
{
::HeapFree(::GetProcessHeap(), 0, pv);
}
/*--------------------------------------------------------------------------+
| Logging to event logs |
+--------------------------------------------------------------------------*/
void
LogEvent(WORD wType, DWORD dwEventID, char *sz)
{
HANDLE hsrc;
hsrc = RegisterEventSource(NULL, g_szSSOProgID);
if (!hsrc)
return;
ReportEvent(hsrc, wType, 0, dwEventID, NULL, 1, 0, (const char **)&sz, NULL);
DeregisterEventSource(hsrc);
}
/*--------------------------------------------------------------------------+
| Data Munging |
+--------------------------------------------------------------------------*/
void
_AnsiStringFromGuid(REFGUID rguid, LPSTR sz)
{
wsprintf(sz, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
rguid.Data1, rguid.Data2, rguid.Data3,
rguid.Data4[0], rguid.Data4[1],
rguid.Data4[2], rguid.Data4[3],
rguid.Data4[4], rguid.Data4[5],
rguid.Data4[6], rguid.Data4[7]);
}
/*--------------------------------------------------------------------------+
| String Manipulation / Parsing |
+--------------------------------------------------------------------------*/
LPSTR
_SkipWhiteSpace(LPSTR sz)
{
while (*sz && (*sz == chLF ||
*sz == chCR ||
*sz == chSpace ||
*sz == chTab))
{
sz = CharNext(sz);
}
return(sz);
}
LPSTR
_FindEndOfLine(LPSTR sz)
{
while (*sz && *sz != chLF && *sz != chCR)
sz = CharNext(sz);
return(sz);
}
// UNDONE: Consider using built-in atoi or atol rather than reinventing the wheel below.
DWORD
_atoi(LPSTR sz)
{
DWORD dwRet = 0;
while (*sz &&
((*sz) >= '0') &&
((*sz) <= '9'))
{
dwRet *= 10;
dwRet += (*sz) - '0';
sz++;
}
return(dwRet);
}
/*--------------------------------------------------------------------------+
| URL Fiddling |
+--------------------------------------------------------------------------*/
int
UrlType(char *szUrl)
{
// do the easy ones first
if (*szUrl == '/' && *(szUrl + 1) == '/')
return URL_TYPE_ABSOLUTE;
if (*szUrl == '\\' && *(szUrl + 1) == '\\')
return URL_TYPE_ABSOLUTE;
if (*szUrl == '/' || *szUrl == '\\')
return URL_TYPE_LOCAL_ABSOLUTE;
// okay, now we just need to distinguish between http:stuff and
// foo/bar/baz.html. we do this by looking for a colon.
// it will be good enough for now.
while (*szUrl)
{
if (*szUrl == ':')
return URL_TYPE_ABSOLUTE;
szUrl++;
}
return URL_TYPE_RELATIVE;
}
/*--------------------------------------------------------------------------+
| IIS Hacks |
+--------------------------------------------------------------------------*/
BOOL
_FTranslateVirtualRoot(EXTENSION_CONTROL_BLOCK *pecb, LPSTR szPathIn,
LPSTR szPathTranslated, DWORD cbPathTranslated)
{
BOOL fRet;
lstrcpy(szPathTranslated, szPathIn);
fRet = pecb->ServerSupportFunction(pecb->ConnID,
HSE_REQ_MAP_URL_TO_PATH,
szPathTranslated,
&cbPathTranslated, NULL);
return(fRet);
}
/*--------------------------------------------------------------------------+
| CThingWatcher/CFileWatcher/CRegKeyWatcher |
+--------------------------------------------------------------------------*/
#define ihNotify 0
#define ihSuicide 1
DWORD
ThingWatcherThread(CThingWatcher *ptw)
{
HANDLE rghWait[2];
HINSTANCE hModule;
DWORD dwErr;
PFNCLOSEHEVTNOTIFY pfnCloseHevtNotify;
if (DuplicateHandle(GetCurrentProcess(),
ptw->m_rghWait[ihSuicide],
GetCurrentProcess(),
&(rghWait[ihSuicide]),
0,
FALSE,
DUPLICATE_SAME_ACCESS) == FALSE)
{
return 0;
}
hModule = ptw->m_hModule; ptw->m_hModule = NULL;
// I own this handle now
//
rghWait[ihNotify] = ptw->m_rghWait[ihNotify]; ptw->m_rghWait[ihNotify] = INVALID_HANDLE_VALUE;
pfnCloseHevtNotify = ptw->m_pfnCloseHevtNotify;
LWaitSomeMore:
dwErr = ::WaitForMultipleObjects(2, rghWait, FALSE, INFINITE);
// careful! We only signal our pfn if the NOTIFY handle
// gets set... if we error out, or the SUICIDE handle is set,
// or any other cruft, we just go away quietly.
if (dwErr == (WAIT_OBJECT_0 + ihNotify))
{
// Wait again ?
if (ptw->FireChange(dwErr) == TRUE)
goto LWaitSomeMore;
}
// Close the notify handle
//
pfnCloseHevtNotify(rghWait[ihNotify]);
// Close the suicide handle
//
CloseHandle(rghWait[ihSuicide]);
// Unload the library
//
FreeLibraryAndExitThread(hModule, 0);
return(0);
}
CThingWatcher::CThingWatcher(PFNCLOSEHEVTNOTIFY pfnCloseHevtNotify)
{
m_rghWait[ihNotify] = INVALID_HANDLE_VALUE;
m_rghWait[ihSuicide] = NULL;
m_hModule = NULL;
m_pfnCloseHevtNotify = pfnCloseHevtNotify;
}
CThingWatcher::~CThingWatcher()
{
if (m_rghWait[ihSuicide] != NULL)
{
::SetEvent(m_rghWait[ihSuicide]);
::CloseHandle(m_rghWait[ihSuicide]);
}
// If the thread is already running, this will not happen.
//
if (m_rghWait[ihNotify] != INVALID_HANDLE_VALUE)
m_pfnCloseHevtNotify(m_rghWait[ihNotify]);
#ifdef DEBUG
::FillMemory(this, sizeof(this), 0xAC);
#endif
}
BOOL
CThingWatcher::FWatchHandle(HANDLE hevtNotify)
{
HANDLE hThread;
DWORD dwTid;
char szModulePath[512];
if (m_rghWait[ihSuicide] == NULL)
m_rghWait[ihSuicide] = IIS_CREATE_EVENT(
"CThingWatcher::m_rghWait[ihSuicide]",
this,
TRUE,
FALSE
);
m_rghWait[ihNotify] = hevtNotify;
if (!m_rghWait[ihSuicide] || m_rghWait[ihNotify] == INVALID_HANDLE_VALUE)
{
return(FALSE);
}
if (GetModuleFileName(g_hinst, szModulePath, sizeof(szModulePath)) == 0)
return FALSE;
m_hModule = LoadLibrary(szModulePath);
if (m_hModule == NULL)
return FALSE;
hThread = ::CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE) ThingWatcherThread,
(LPVOID) this,
0,
&dwTid);
if (hThread == NULL)
{
FreeLibrary(m_hModule);
return(FALSE);
}
::CloseHandle(hThread);
return TRUE;
}
CFileWatcher::CFileWatcher() : CThingWatcher(FindCloseChangeNotification)
{
m_szPath[0] = 0;
}
CFileWatcher::~CFileWatcher()
{
#ifdef DEBUG
::FillMemory(this, sizeof(this), 0xAC);
#endif
}
BOOL
CFileWatcher::FStartWatching(LPSTR szPath, LONG lUser, PFNFILECHANGED pfnChanged)
{
HANDLE hfile;
DWORD dwAttr;
char szPathDir[MAX_PATH];
char *pch;
// remember settings
lstrcpy(m_szPath, szPath);
m_lUser = lUser;
m_pfnChanged = pfnChanged;
// see if it's a directory
dwAttr = ::GetFileAttributes(szPath);
if (dwAttr == 0xFFFFFFFF)
return(FALSE);
if (dwAttr & FILE_ATTRIBUTE_DIRECTORY)
{
m_fDirectory = TRUE;
lstrcpy(szPathDir, m_szPath);
}
else
{
m_fDirectory = FALSE;
hfile = ::CreateFile(m_szPath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hfile == INVALID_HANDLE_VALUE)
return(FALSE);
::GetFileTime(hfile, NULL, NULL, &m_ftLastWrite);
::CloseHandle(hfile);
lstrcpy(szPathDir, m_szPath);
pch = szPathDir + lstrlen(szPathDir) - 1;
while (*pch != '\\')
pch = ::CharPrev(szPathDir, pch);
*pch = 0;
}
m_hevtNotify = ::FindFirstChangeNotification(szPathDir,
FALSE,
FILE_NOTIFY_CHANGE_LAST_WRITE);
return this->FWatchHandle(m_hevtNotify);
}
// note: most of this was moved out of FileWatcherThread
BOOL
CFileWatcher::FireChange(DWORD dwWait)
{
HANDLE hfile;
FILETIME ftLastWriteNow;
if (m_fDirectory)
{
// just watching a directory. If they say something
// changed, then something changed!
goto LDoChange;
}
else
{
// watching a specific file ... check last write times
hfile = ::CreateFile(m_szPath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hfile == INVALID_HANDLE_VALUE)
{
// file is gone; that sure is changed now, isn't it...
goto LDoChange;
}
else
{
::GetFileTime(hfile, NULL, NULL, &ftLastWriteNow);
::CloseHandle(hfile);
if (ftLastWriteNow.dwLowDateTime != m_ftLastWrite.dwLowDateTime ||
ftLastWriteNow.dwHighDateTime != m_ftLastWrite.dwHighDateTime)
{
// file has changed...
goto LDoChange;
}
else
{
// oh great; some other file changed.
::FindNextChangeNotification(m_hevtNotify);
return TRUE;
}
}
}
LDoChange:
if (m_pfnChanged)
(m_pfnChanged)(m_szPath, m_lUser);
return FALSE;
}
CRegKeyWatcher::CRegKeyWatcher() : CThingWatcher(CloseHandle)
{
}
CRegKeyWatcher::~CRegKeyWatcher()
{
#ifdef DEBUG
::FillMemory(this, sizeof(this), 0xAC);
#endif
}
//$ note: if the RegNotifyChangeKeyValue fails, it's because we're on win95.
//$ i could do a polling fallback!
BOOL
CRegKeyWatcher::FStartWatching(HKEY hkey, BOOL fSubTree, DWORD dwNotifyFilter, LONG lUser, PFNREGKEYCHANGED pfnChanged)
{
m_hkey = hkey;
m_lUser = lUser;
m_pfnChanged = pfnChanged;
m_hevtNotify = IIS_CREATE_EVENT(
"CRegKeyWatcher::m_hevtNotify",
this,
FALSE,
FALSE
);
if (ERROR_SUCCESS != RegNotifyChangeKeyValue(hkey, fSubTree, dwNotifyFilter, m_hevtNotify, TRUE))
return FALSE;
return this->FWatchHandle(m_hevtNotify);
}
BOOL
CRegKeyWatcher::FireChange(DWORD dwWait)
{
// CloseHandle(m_hevtNotify);
if (m_pfnChanged)
m_pfnChanged(m_hkey, m_lUser);
return FALSE;
}
/*--------------------------------------------------------------------------+
| CDataFile |
+--------------------------------------------------------------------------*/
CDataFile::CDataFile(LPSTR szDataPath, CDataFileGroup *pfg)
{
m_cRef = 0;
m_pdfNext = m_pdfPrev = NULL;
lstrcpy(m_szDataPath, szDataPath);
m_pfg = pfg;
}
CDataFile::~CDataFile()
{
#ifdef DEBUG
::FillMemory(this, sizeof(this), 0xAC);
#endif
}
ULONG
CDataFile::AddRef()
{
ULONG cRefNew;
m_cs.Lock();
cRefNew = ++m_cRef;
m_cs.Unlock();
return(cRefNew);
}
ULONG
CDataFile::Release()
{
ULONG cRefNew;
m_cs.Lock();
cRefNew = --m_cRef;
m_cs.Unlock();
if (!cRefNew)
this->FreeDataFile();
return(cRefNew);
}
ULONG
CDataFile::GetRefCount()
{
return(m_cRef);
}
void
DataFileChanged(LPSTR szFile, LONG lUser)
{
CDataFile *pdf = (CDataFile*) lUser;
pdf->m_pfg->ForgetDataFile(pdf);
}
BOOL
CDataFile::FWatchFile(PFNFILECHANGED pfnChanged)
{
PFNFILECHANGED pfn;
pfn = (pfnChanged ? pfnChanged : (PFNFILECHANGED) DataFileChanged);
return(m_fw.FStartWatching(m_szDataPath, (LONG) this, pfn));
}
BOOL
CDataFile::FMatch(LPSTR szDataPath)
{
return(lstrcmpi(szDataPath, m_szDataPath) == 0);
}
/*--------------------------------------------------------------------------+
| CDataFileGroup |
+--------------------------------------------------------------------------*/
CDataFileGroup::CDataFileGroup()
{
::FillMemory(m_rghb, NUM_GROUP_BUCKETS * sizeof(HB), 0);
}
CDataFileGroup::~CDataFileGroup()
{
HB *phb;
DWORD ihb;
// clean up leftovers
for (ihb = 0, phb = m_rghb; ihb < NUM_GROUP_BUCKETS; ++ihb, ++phb)
{
while (phb->pdfHead)
this->ForgetDataFile(phb->pdfHead);
}
#ifdef DEBUG
::FillMemory(this, sizeof(this), 0xAC);
#endif
}
CDataFile *
CDataFileGroup::GetDataFile(LPSTR szDataPath)
{
HB *phb;
CDataFile *pdf = NULL;
phb = this->GetHashBucket(szDataPath);
m_cs.Lock();
// try to find it
for (pdf = phb->pdfHead; pdf && !pdf->FMatch(szDataPath); pdf = pdf->GetNext())
/* nothing */ ;
// if not on the list, create a new one and remember it
if (!pdf)
{
if (pdf = this->CreateDataFile(szDataPath))
this->RememberDataFile(pdf, phb);
}
// if we got one, addref it
if (pdf)
pdf->AddRef();
m_cs.Unlock();
return(pdf);
}
void
CDataFileGroup::RememberDataFile(CDataFile *pdf, HB *phb)
{
if (!phb)
phb = this->GetHashBucket(pdf->m_szDataPath);
m_cs.Lock();
pdf->SetNext(phb->pdfHead);
pdf->SetPrev(NULL);
if (phb->pdfHead)
phb->pdfHead->SetPrev(pdf);
phb->pdfHead = pdf;
if (!phb->pdfTail)
phb->pdfTail = pdf;
m_cs.Unlock();
pdf->AddRef();
}
void
CDataFileGroup::ForgetDataFile(CDataFile *pdf)
{
HB *phb = this->GetHashBucket(pdf->m_szDataPath);
m_cs.Lock();
if (pdf->GetNext())
pdf->GetNext()->SetPrev(pdf->GetPrev());
if (pdf->GetPrev())
pdf->GetPrev()->SetNext(pdf->GetNext());
if (phb->pdfHead == pdf)
phb->pdfHead = pdf->GetNext();
if (phb->pdfTail == pdf)
phb->pdfTail = pdf->GetPrev();
m_cs.Unlock();
pdf->Release();
}
HB*
CDataFileGroup::GetHashBucket(LPSTR szDataPath)
{
DWORD dwSum = 0;
char *pch;
// nyi - a real hash function
for (pch = szDataPath; *pch; ++pch)
dwSum += (DWORD) *pch;
return(&(m_rghb[dwSum % NUM_GROUP_BUCKETS]));
}
/*--------------------------------------------------------------------------+
| CResourceCollection |
+--------------------------------------------------------------------------*/
CResourceCollection::CResourceCollection()
{
m_crs = 0;
m_rgrs = NULL;
}
CResourceCollection::~CResourceCollection()
{
//$ Assert(!m_rgrs);
}
// public
BOOL
CResourceCollection::FInit(int cRsrc)
{
m_rgrs = (PRS)_MsnAlloc(cRsrc * sizeof(RS));
if (!m_rgrs)
return FALSE;
FillMemory(m_rgrs, cRsrc * sizeof(RS), 0);
m_crs = cRsrc;
m_hsem = IIS_CREATE_SEMAPHORE(
"CResourceCollection::m_hsem",
this,
cRsrc,
cRsrc
);
if (!m_hsem)
{
_MsnFree(m_rgrs);
m_rgrs = NULL;
return FALSE;
}
return TRUE;
}
// public
BOOL
CResourceCollection::FTerm()
{
if (m_rgrs)
{
this->CleanupAll(NULL); // pvNil value doesn't matter here
_MsnFree(m_rgrs);
m_rgrs = NULL;
}
return TRUE;
}
// private
// returns a free rs if there is one; otherwise returns NULL.
// if there is a free rs, but it's not yet inited, tries to init.
PRS
CResourceCollection::PrsFree()
{
PRS prs = NULL;
int irs;
m_cs.Lock();
for (irs = 0; irs < m_crs; irs++)
{
if (!(m_rgrs[irs].fInUse))
{
prs = &m_rgrs[irs];
prs->fInUse = TRUE;
break;
}
}
if (prs && !prs->fValid)
{
prs->fValid = this->FInitResource(prs);
//$ if this fails, should i just return null? or let the client decide?
}
m_cs.Unlock();
return prs;
}
// public
//$ maybe limit # of retries?
//$ maybe have way to retry the FInitResource if it fails?
HRS
CResourceCollection::HrsGetResource()
{
PRS prs;
LTryAgain:
this->WaitForRs();
prs = this->PrsFree();
if (prs && !prs->fValid)
{
this->ReleaseResource(FALSE, (HRS)prs);
return hrsNil;
}
if (prs)
return (HRS)prs;
this->WaitForRs();
goto LTryAgain;
}
// private
// waits until the release-resource semaphore goes off
//$ timeouts? way to stop waiting?
void
CResourceCollection::WaitForRs()
{
WaitForSingleObject(m_hsem, INFINITE);
}
// public
void
CResourceCollection::ReleaseResource(BOOL fReset, HRS hrs)
{
PRS prs = (PRS)hrs;
if (!prs)
return;
if (fReset && prs->fValid)
{
m_cs.Lock();
this->CleanupResource(prs);
prs->fValid = FALSE;
m_cs.Unlock();
}
m_cs.Lock();
prs->fInUse = FALSE;
m_cs.Unlock();
ReleaseSemaphore(m_hsem, 1, NULL);
}
// public
void
CResourceCollection::CleanupAll(VOID *pvNil)
{
int i;
PRS prs;
m_cs.Lock();
for (i = 0, prs = m_rgrs; i < m_crs; i++, prs++)
{
this->CleanupResource(prs);
prs->fInUse = FALSE;
prs->fValid = FALSE;
prs->pv = pvNil;
}
m_cs.Unlock();
}
// public
BOOL
CResourceCollection::FValid(HRS hrs)
{
PRS prs = (PRS) hrs;
if (!prs)
return FALSE;
return prs->fValid;
}