/* ** WCSUTIL.CPP ** Sean Q. Nolan ** ** Web Component Team Server-Side Utilities */ #pragma warning(disable: 4237) // disable "bool" reserved #include #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; }