// // CallLog.cpp // // Created: ChrisPi 10-17-96 // Updated: RobD 10-30-96 // // ToDo: // - Expire records // - UI to delete record(s) // - system policy? // #include "precomp.h" #include "rostinfo.h" #include "CallLog.h" #include "particip.h" // for MAX_PARTICIPANT_NAME #include "ConfUtil.h" #define MAX_DELETED_ENTRIES_BEFORE_REWRITE 10 #define LARGE_ENTRY_SIZE 256 CCallLogEntry::CCallLogEntry( LPCTSTR pcszName, DWORD dwFlags, CRosterInfo* pri, LPVOID pvRosterData, PBYTE pbCert, ULONG cbCert, LPSYSTEMTIME pst, DWORD dwFileOffset) : m_dwFlags (dwFlags), m_pri (NULL), m_pbCert (NULL), m_cbCert (0), m_dwFileOffset (dwFileOffset) { DebugEntry(CCallLogEntry::CCallLogEntry); ASSERT(NULL != pcszName); ASSERT(NULL != pst); // Only one of these two parameters should be non-NULL ASSERT((NULL == pvRosterData) || (NULL == pri)); LPVOID pvData = pvRosterData; if (NULL != pri) { UINT cbData; if (SUCCEEDED(pri->Save(&pvData, &cbData))) { ASSERT(pvData); } } if (NULL != pvData) { m_pri = new CRosterInfo(); if (NULL != m_pri) { m_pri->Load(pvData); } } if (NULL != pbCert && 0 != cbCert) { m_pbCert = new BYTE[cbCert]; if (NULL == m_pbCert) { ERROR_OUT(("CCalllogEntry::CCalllogEntry() -- failed to allocate memory")); } else { memcpy(m_pbCert, pbCert, cbCert); m_cbCert = cbCert; } } m_st = *pst; m_pszName = PszAlloc(pcszName); DebugExitVOID(CCallLogEntry::CCallLogEntry); } CCallLogEntry::~CCallLogEntry() { DebugEntry(CCallLogEntry::~CCallLogEntry); delete m_pszName; // NOTE: m_pri must be new'ed by the function that calls the // constructor - this is an optimization to avoid unnecessary // copying - but it's a little unclean. delete m_pri; delete []m_pbCert; DebugExitVOID(CCallLogEntry::~CCallLogEntry); } ///////////////////////////////////////////////////////////////////////// CCallLog::CCallLog(LPCTSTR pszKey, LPCTSTR pszDefault) : m_fUseList (FALSE), m_fDataRead (FALSE), m_cTotalEntries (0), m_cDeletedEntries (0) { InitLogData(pszKey, pszDefault); TRACE_OUT(("Using Call Log file [%s]", m_strFile)); } CCallLog::~CCallLog() { DebugEntry(CCallLog::~CCallLog); // Check to see if we are more than a number entries over our // configured maximum and re-write the file if so TRACE_OUT(("Entry count: total:%d deleted:%d", m_cTotalEntries, m_cDeletedEntries)); if ( m_fUseList && m_cDeletedEntries > MAX_DELETED_ENTRIES_BEFORE_REWRITE ) RewriteFile(); else { int size = GetSize(); for( int i = 0; i < size; i++ ) { ASSERT( NULL != (*this)[i] ); delete (*this)[i]; } } DebugExitVOID(CCallLog::~CCallLog); } /* A D D C A L L */ /*------------------------------------------------------------------------- %%Function: AddCall -------------------------------------------------------------------------*/ HRESULT CCallLog::AddCall(LPCTSTR pcszName, PLOGHDR pLogHdr, CRosterInfo* pri, PBYTE pbCert, ULONG cbCert) { TRACE_OUT( ("CCallLog::AddCall(\"%s\")", pcszName) ); DWORD dwFileOffset; HRESULT hr = S_OK; ASSERT(NULL != pLogHdr); // Grab the current local time ::GetLocalTime(&(pLogHdr->sysTime)); ApiDebugMsg(("CALL_LOG: [%s] %s", pcszName, (pLogHdr->dwCLEF & CLEF_ACCEPTED) ? "ACCEPTED" : "REJECTED")); // Append the data to the file dwFileOffset = WriteEntry(pcszName, pLogHdr, pri, pbCert, cbCert); TRACE_OUT(("AddCall: adding entry with %d total, %d deleted, %d max", m_cTotalEntries, m_cDeletedEntries, m_cMaxEntries )); // Create list entry only when necessary if (m_fUseList) { CCallLogEntry* pcleNew = new CCallLogEntry( pcszName, pLogHdr->dwCLEF, pri, NULL, pbCert, cbCert, &(pLogHdr->sysTime), dwFileOffset); if (NULL == pcleNew) return E_OUTOFMEMORY; Add(pcleNew); m_cTotalEntries++; // Check to see if this put us over the top of valid entries // and remove the oldest entry if so if ( m_cTotalEntries - m_cDeletedEntries > m_cMaxEntries ) { // Remove oldest entry DeleteEntry((*this)[0]); RemoveAt( 0 ); } } else { // Check to see if the file is getting large based on // our target number of entries and a heuristic large // entry size. If our file has grown over this point, load // the file and trim the list so that we will re-write // a smaller file on exit. TRACE_OUT(("Checking file size %d against %d * %d", dwFileOffset, m_cMaxEntries, LARGE_ENTRY_SIZE)); if ( dwFileOffset > (DWORD)( m_cMaxEntries * LARGE_ENTRY_SIZE ) ) { TRACE_OUT(("Log file getting large, forcing LoadFileData")); LoadFileData(); } } return S_OK; } /* I N I T L O G D A T A */ /*------------------------------------------------------------------------- %%Function: InitLogData Get the log data from the registry. - the expiration information - the file name (with path) If there is no entry for the file name, a new, unique file is created. -------------------------------------------------------------------------*/ VOID CCallLog::InitLogData(LPCTSTR pszKey, LPCTSTR pszDefault) { TCHAR szPath[MAX_PATH]; PTSTR pszFileName; HANDLE hFile; ASSERT(m_strFile.IsEmpty()); RegEntry reLog(pszKey, HKEY_CURRENT_USER); m_Expire = reLog.GetNumber(REGVAL_LOG_EXPIRE, 0); m_strFile = reLog.GetString(REGVAL_LOG_FILE); m_cMaxEntries = reLog.GetNumber(REGVAL_LOG_MAX_ENTRIES, DEFAULT_LOG_MAX_ENTRIES ); TRACE_OUT(("Max Entries set to %d", m_cMaxEntries )); // Make sure file exists and can be read/written hFile = OpenLogFile(); if (NULL != hFile) { // valid file found CloseHandle(hFile); return; } // String is invalid (or empty) - make sure it's empty m_strFile.Empty(); // Create the new log file in the NetMeeting directory if (!GetInstallDirectory(szPath)) { WARNING_OUT(("InitLogData: Unable to get Install Directory?")); return; } pszFileName = &szPath[lstrlen(szPath)]; // Try to use the default name wsprintf(pszFileName, TEXT("%s%s"), pszDefault, TEXT(".dat")); hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) { // Use a unique name to avoid other users' files for (int iFile = 2; iFile < 999; iFile++) { wsprintf(pszFileName, TEXT("%s%d.dat"), pszDefault, iFile); hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE != hFile) break; switch (GetLastError()) { case ERROR_FILE_EXISTS: // We get this with NT case ERROR_ALREADY_EXISTS: // and this with Win95 break; default: WARNING_OUT(("Unable to create log file [%s] err=0x%08X", szPath, GetLastError())); break; } /* switch (GetLastError()) */ } } if (INVALID_HANDLE_VALUE != hFile) { CloseHandle(hFile); m_strFile = szPath; reLog.SetValue(REGVAL_LOG_FILE, szPath); } } /* O P E N L O G F I L E */ /*------------------------------------------------------------------------- %%Function: OpenLogFile Open the log file and return a handle to file. Return NULL if there was a problem. -------------------------------------------------------------------------*/ HANDLE CCallLog::OpenLogFile(VOID) { HANDLE hFile; if (m_strFile.IsEmpty()) { WARNING_OUT(("Problem opening call log file")); return NULL; } hFile = CreateFile(m_strFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) { ERROR_OUT(("OpenLogFile: Unable to open call log file")); hFile = NULL; } return hFile; } /* R E A D D A T A */ /*------------------------------------------------------------------------- %%Function: ReadData -------------------------------------------------------------------------*/ BOOL CCallLog::ReadData(HANDLE hFile, PVOID pv, UINT cb) { DWORD cbRead; ASSERT(NULL != hFile); ASSERT(NULL != pv); if (0 == cb) return TRUE; if (!ReadFile(hFile, pv, cb, &cbRead, NULL)) return FALSE; return (cb == cbRead); } /* W R I T E D A T A */ /*------------------------------------------------------------------------- %%Function: WriteData Write the data to the file. The file will be automatically opened/close if hFile is NULL. -------------------------------------------------------------------------*/ HRESULT CCallLog::WriteData(HANDLE hFile, LPDWORD pdwOffset, PVOID pv, DWORD cb) { HRESULT hr = E_FAIL; HANDLE hFileTemp = NULL; DWORD cbWritten; if (0 == cb) return S_OK; // nothing to do ASSERT(NULL != pv); ASSERT(NULL != pdwOffset); ASSERT(INVALID_FILE_SIZE != *pdwOffset); if (NULL == hFile) { // Auto-open the file, if necessary hFileTemp = OpenLogFile(); if (NULL == hFileTemp) return E_FAIL; hFile = hFileTemp; } ASSERT(INVALID_HANDLE_VALUE != hFile); if (INVALID_FILE_SIZE != SetFilePointer(hFile, *pdwOffset, NULL, FILE_BEGIN)) { if (WriteFile(hFile, pv, cb, &cbWritten, NULL) && (cb == cbWritten)) { *pdwOffset += cbWritten; hr = S_OK; } } if (NULL != hFileTemp) { // Close the temporary file handle CloseHandle(hFileTemp); } return hr; } /* W R I T E E N T R Y */ /*------------------------------------------------------------------------- %%Function: WriteEntry Write a call log entry to the end of the log file. The function returns the file position at which the data was written or INVALID_FILE_SIZE (0xFFFFFFFF) if there was a problem. -------------------------------------------------------------------------*/ DWORD CCallLog::WriteEntry(LPCTSTR pcszName, PLOGHDR pLogHdr, CRosterInfo* pri, PBYTE pbCert, ULONG cbCert) { PVOID pvData; HANDLE hFile; DWORD dwFilePosition; DWORD dwPos; BSTR pcwszName; ASSERT(NULL != pcszName); ASSERT(NULL != pLogHdr); hFile = OpenLogFile(); if (NULL == hFile) return INVALID_FILE_SIZE; dwFilePosition = SetFilePointer(hFile, 0, NULL, FILE_END); if (INVALID_FILE_SIZE != dwFilePosition) { dwPos = dwFilePosition; // Always write display name in UNICODE if(SUCCEEDED(LPTSTR_to_BSTR(&pcwszName, pcszName))) { pLogHdr->cbName = (lstrlenW((LPWSTR)pcwszName) + 1) * sizeof(WCHAR); if ((NULL == pri) || (!SUCCEEDED(pri->Save(&pvData, (UINT *) &(pLogHdr->cbData)))) ) { // No data? pLogHdr->cbData = 0; pvData = NULL; } pLogHdr->cbCert = cbCert; // Calculate total size of record pLogHdr->dwSize = sizeof(LOGHDR) + pLogHdr->cbName + pLogHdr->cbData + pLogHdr->cbCert; if ((S_OK != WriteData(hFile, &dwPos, pLogHdr, sizeof(LOGHDR))) || (S_OK != WriteData(hFile, &dwPos, pcwszName, pLogHdr->cbName)) || (S_OK != WriteData(hFile, &dwPos, pvData, pLogHdr->cbData)) || (S_OK != WriteData(hFile, &dwPos, pbCert, cbCert))) { dwFilePosition = INVALID_FILE_SIZE; } SysFreeString(pcwszName); } } CloseHandle(hFile); return dwFilePosition; } /* R E A D E N T R Y */ /*------------------------------------------------------------------------- %%Function: ReadEntry Read the next entry from the file. *ppcle will be set to NULL if the entry was deleted. Return Values: S_OK - data was read successfully S_FALSE - data exists, but was deleted E_FAIL - problem reading file -------------------------------------------------------------------------*/ HRESULT CCallLog::ReadEntry(HANDLE hFile, DWORD * pdwFileOffset, CCallLogEntry** ppcle) { DWORD dwOffsetSave; LOGHDR logHdr; WCHAR wszName[MAX_PARTICIPANT_NAME]; ASSERT(NULL != ppcle); ASSERT(NULL != hFile); ASSERT(NULL != pdwFileOffset); *ppcle = NULL; // initialize this in case we return with an error dwOffsetSave = *pdwFileOffset; if (INVALID_FILE_SIZE == SetFilePointer(hFile, dwOffsetSave, NULL, FILE_BEGIN)) return E_FAIL; // Read record header if (!ReadData(hFile, &logHdr, sizeof(LOGHDR)) ) return E_FAIL; // Return pointer to end of record *pdwFileOffset += logHdr.dwSize; if (logHdr.dwCLEF & CLEF_DELETED) { // Skip deleted record ASSERT(NULL == *ppcle); return S_FALSE; } if (logHdr.cbName > sizeof(wszName)) logHdr.cbName = sizeof(wszName); // Read Name if (!ReadData(hFile, wszName, logHdr.cbName)) return E_FAIL; // Read Extra Data PVOID pvData = NULL; if (logHdr.cbData != 0) { pvData = new BYTE[logHdr.cbData]; if (NULL != pvData) { if (!ReadData(hFile, pvData, logHdr.cbData)) { WARNING_OUT(("Problem reading roster data from log")); } } } PBYTE pbCert = NULL; if ((logHdr.dwCLEF & CLEF_SECURE ) && logHdr.cbCert != 0) { pbCert = new BYTE[logHdr.cbCert]; if (NULL != pbCert) { if (!ReadData(hFile, pbCert, logHdr.cbCert)) { WARNING_OUT(("Problem reading certificate data from log")); } } } BSTR bstrName = ::SysAllocString(wszName); if(bstrName) { LPTSTR szName; HRESULT hr = BSTR_to_LPTSTR (&szName, bstrName); if (SUCCEEDED(hr)) { // Create the new log entry from the data read *ppcle = new CCallLogEntry(szName, logHdr.dwCLEF, NULL, pvData, pbCert, logHdr.cbCert, &logHdr.sysTime, dwOffsetSave); delete szName; } SysFreeString(bstrName); } delete [] pvData; delete [] pbCert; return S_OK; } /* L O A D F I L E D A T A */ /*------------------------------------------------------------------------- %%Function: LoadFileData Load the call log data from the file -------------------------------------------------------------------------*/ VOID CCallLog::LoadFileData(VOID) { HANDLE hFile; DWORD dwFileOffset; CCallLogEntry * pcle; hFile = OpenLogFile(); if (NULL == hFile) return; m_cTotalEntries = 0; m_cDeletedEntries = 0; dwFileOffset = 0; while (E_FAIL != ReadEntry(hFile, &dwFileOffset, &pcle)) { m_cTotalEntries++; if (NULL == pcle) { m_cDeletedEntries++; continue; // deleted record } Add(pcle); TRACE_OUT(("Read Entry: \"%s\" (%02d/%02d/%04d %02d:%02d:%02d) : %s", pcle->m_pszName, pcle->m_st.wMonth, pcle->m_st.wDay, pcle->m_st.wYear, pcle->m_st.wHour, pcle->m_st.wMinute, pcle->m_st.wSecond, (CLEF_ACCEPTED & pcle->m_dwFlags) ? "ACCEPTED" : "REJECTED")); } CloseHandle(hFile); m_fUseList = TRUE; m_fDataRead = TRUE; // Now trim the list down to our configured maximum if // the count exceeds our target. The file will be compacted // when we write it out if we have more than a few deleted // entries. for( int nn = 0, delCount = m_cTotalEntries - m_cDeletedEntries - m_cMaxEntries; nn < delCount; nn++ ) { DeleteEntry((*this)[0]); RemoveAt( 0 ); } } /* R E W R I T E F I L E */ /*------------------------------------------------------------------------- %%Function: RewriteFile Re-write the log file from the in-memory list, compress deleted entries -------------------------------------------------------------------------*/ VOID CCallLog::RewriteFile(VOID) { HANDLE hFile; TRACE_OUT(("Rewriting log file")); // Make sure we don't nuke the file without a list to write out ASSERT(m_fUseList); // Reset the file pointer and write the EOF marker if (!m_strFile.IsEmpty()) { hFile = OpenLogFile(); if (NULL != hFile) { if (INVALID_FILE_SIZE != SetFilePointer(hFile, 0, NULL, FILE_BEGIN)) { SetEndOfFile(hFile); m_cTotalEntries = 0; m_cDeletedEntries = 0; } CloseHandle(hFile); } } // Write out all non-deleted records for( int i = 0; i < GetSize(); ++i ) { CCallLogEntry* pcle = (*this)[i]; ASSERT(NULL != pcle); LOGHDR LogHdr; // Initialize LogHdr items from memory object LogHdr.dwCLEF = pcle->GetFlags(); LogHdr.dwPF = 0; LogHdr.sysTime = *pcle->GetTime(); // Write out entry WriteEntry( pcle->m_pszName, &LogHdr, pcle->m_pri, pcle->m_pbCert, pcle->m_cbCert); delete pcle; } } // Grumble... Inefficient HRESULT CCallLog::DeleteEntry(CCallLogEntry * pcle) { HRESULT hr; DWORD dwFlags; DWORD dwOffset; if (NULL == pcle) { WARNING_OUT(("DeleteEntry: Unable to find entry")); return E_FAIL; } // Calculate offset to "CLEF" dwOffset = pcle->GetFileOffset() + offsetof(LOGHDR, dwCLEF); dwFlags = pcle->GetFlags() | CLEF_DELETED; hr = WriteData(NULL, &dwOffset, &dwFlags, sizeof(DWORD)); m_cDeletedEntries++; TRACE_OUT(("Marked [%s] pos=%08X for deletion", pcle->GetName(), pcle->GetFileOffset() )); delete pcle; return hr; }