/*++ Copyright (c) 1992-1997 Microsoft Corporation Module Name: xsaction.c Abstract: Provides basic xscational services for cluster logging. Author: Sunita Shrivastava (sunitas) 17-Mar-1997 Revision History: --*/ #include "service.h" #include "lmp.h" /**** @doc EXTERNAL INTERFACES CLUSSVC LM ****/ /**** @func HXSACTION | LogStartXsaction| Write a start transaction record to the log. @parm IN HLOG | hLog | Supplies the handle to the log. @parm IN TRID | TrId | Supplies the transaction id. @parm IN RMID | ResourceId | The resource id that identifies the resource manager. @parm IN RMTYPE | ResourceFlags | A dword of flags that the resource manager may use to store any data it wants with this record. @rdesc Returns a handle suitable for use in subsequent log calls. NUll in case failure. Call GetLastError() to get the error. @xref ****/ HXSACTION LogStartXsaction( IN HLOG hLog, IN TRID TrId, IN RMID ResourceId, IN RMTYPE ResourceFlags ) { PLOG pLog; DWORD dwError=ERROR_SUCCESS; LSN Lsn = NULL_LSN; PLOGRECORD pLogRecord; DWORD dwNumPages; PLOGPAGE pPage; DWORD dwTotalSize; BOOL bMaxFileSizeReached; PXSACTION pXsaction = NULL; GETLOG(pLog, hLog); //write the record, dont allow resets to happen ClRtlLogPrint(LOG_NOISE, "[LM] LogStartXsaction : Entry TrId=%1!u! RmId=%2!u! RmType = %3!u!\r\n", TrId, ResourceId, ResourceFlags); EnterCriticalSection(&pLog->Lock); pXsaction = (PXSACTION)LocalAlloc(LMEM_FIXED, sizeof(XSACTION)); if (!pXsaction) { dwError = ERROR_NOT_ENOUGH_MEMORY; goto FnExit; } #if DBG { DWORD dwOldProtect; DWORD Status; BOOL VPWorked; VPWorked = VirtualProtect(pLog->ActivePage, pLog->SectorSize, PAGE_READWRITE, &dwOldProtect); Status = GetLastError(); CL_ASSERT( VPWorked ); } #endif //reset the file dwError = LogReset(hLog); if (dwError != ERROR_SUCCESS) { ClRtlLogPrint(LOG_NOISE, "[LM] LogStartXsaction : LogReset failed\r\n"); goto FnExit; } #if DBG { DWORD dwOldProtect; DWORD Status; BOOL VPWorked; VPWorked = VirtualProtect(pLog->ActivePage, pLog->SectorSize, PAGE_READWRITE, &dwOldProtect); Status = GetLastError(); CL_ASSERT( VPWorked ); } #endif CL_ASSERT(ResourceId > RMAny); // reserved for logger's use dwTotalSize = sizeof(LOGRECORD) + 7 & ~7; // round up to qword size pPage = LogpAppendPage(pLog, dwTotalSize, &pLogRecord, &bMaxFileSizeReached, &dwNumPages); //we just reset the file, if it cant take the new startxsaction //record, something is awfully wrong !!! if (pPage == NULL) { dwError = GetLastError(); ClRtlLogPrint(LOG_NOISE, "[LM] LogStartXsaction : LogpAppendPage failed.\r\n"); goto FnExit; } CL_ASSERT(((ULONG_PTR)pLogRecord & 0x7) == 0); // ensure qword alignment Lsn = MAKELSN(pPage, pLogRecord); // // Fill in log record. // pLogRecord->Signature = LOGREC_SIG; pLogRecord->ResourceManager = ResourceId; pLogRecord->Transaction = TrId; pLogRecord->XsactionType = TTStartXsaction; pLogRecord->Flags = ResourceFlags; GetSystemTimeAsFileTime(&pLogRecord->Timestamp); pLogRecord->NumPages = dwNumPages; pLogRecord->DataSize = 0; pXsaction->XsactionSig = XSACTION_SIG; pXsaction->TrId = TrId; pXsaction->StartLsn = Lsn; pXsaction->RmId = ResourceId; FnExit: #if DBG { DWORD dwOldProtect; DWORD Status; BOOL VPWorked; VPWorked = VirtualProtect(pLog->ActivePage, pLog->SectorSize, PAGE_READONLY, & dwOldProtect); Status = GetLastError(); CL_ASSERT( VPWorked ); } #endif if (dwError != ERROR_SUCCESS) { if (pXsaction) { LocalFree(pXsaction); pXsaction = NULL; } SetLastError(dwError); } LeaveCriticalSection(&pLog->Lock); ClRtlLogPrint(LOG_NOISE, "[LM] LogStartXsaction : Exit returning=0x%1!08lx!\r\n", Lsn); return((HXSACTION)pXsaction); } /**** @func LSN | LogWriteXsaction| Write a transaction unit record to the log. @parm IN HLOG | hLog | Supplies the handle to the log. @parm IN HXSACTION | hXsaction | Supplies the handle to the transaction. @parm IN RMTYPE | ResourceFlags | A dword of flags that the resource manager may use to store any data it wants with this record. @parm IN PVOID | LogData | Supplies a pointer to the data to be logged. @parm DWORD | DataSize | Supplies the number of bytes of data pointed to by LogData @rdesc The LSN of the log record that was created. NULL_LSN if something terrible happened. GetLastError() will provide the error code. @comm This should use a transaction handle obtained from LogStartXsaction. This call is used to write the parts of a transaction to the quorum log. @xref ****/ LSN LogWriteXsaction( IN HLOG hLog, IN HXSACTION hXsaction, IN RMTYPE ResourceFlags, IN PVOID pLogData, IN DWORD dwDataSize) { PLOG pLog; DWORD dwError=ERROR_SUCCESS; LSN Lsn = NULL_LSN; PLOGRECORD pLogRecord; DWORD dwNumPages; PLOGPAGE pPage; DWORD dwTotalSize; BOOL bMaxFileSizeReached; PXSACTION pXsaction = NULL; GETLOG(pLog, hLog); GETXSACTION(pXsaction, hXsaction); //write the record, dont allow resets to happen ClRtlLogPrint(LOG_NOISE, "[LM] LogWriteXsaction : Entry TrId=%1!u! RmId=%2!u! RmType = %3!u!\r\n", pXsaction->TrId, pXsaction->RmId, ResourceFlags); #if DBG { DWORD dwOldProtect; DWORD Status; BOOL VPWorked; VPWorked = VirtualProtect(pLog->ActivePage, pLog->SectorSize, PAGE_READWRITE, &dwOldProtect); Status = GetLastError(); CL_ASSERT( VPWorked ); } #endif CL_ASSERT(pXsaction->RmId > RMAny); // reserved for logger's use dwTotalSize = sizeof(LOGRECORD) + (dwDataSize+ 7) & ~7; // round up to qword size EnterCriticalSection(&pLog->Lock); pPage = LogpAppendPage(pLog, dwTotalSize, &pLogRecord, &bMaxFileSizeReached, &dwNumPages); //we reset the file in logstartxsaction, if it cant take the new startxsaction //record, something is awfully wrong !!! if (pPage == NULL) { dwError = GetLastError(); //assert if a complete local xsaction extends the log beyond its max size CL_ASSERT( dwError != ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE); ClRtlLogPrint(LOG_NOISE, "[LM] LogWriteXsaction : LogpAppendPage failed.\r\n"); goto FnExit; } CL_ASSERT(((ULONG_PTR)pLogRecord & 0x7) == 0); // ensure qword alignment Lsn = MAKELSN(pPage, pLogRecord); // // Fill in log record. // pLogRecord->Signature = LOGREC_SIG; pLogRecord->ResourceManager = pXsaction->RmId; pLogRecord->Transaction = pXsaction->TrId; pLogRecord->XsactionType = TTXsactionUnit; pLogRecord->Flags = ResourceFlags; GetSystemTimeAsFileTime(&pLogRecord->Timestamp); pLogRecord->NumPages = dwNumPages; pLogRecord->DataSize = dwDataSize; if (dwNumPages < 1) CopyMemory(&pLogRecord->Data, pLogData, dwDataSize); else { if (LogpWriteLargeRecordData(pLog, pLogRecord, pLogData, dwDataSize) != ERROR_SUCCESS) { ClRtlLogPrint(LOG_NOISE, "[LM] LogWriteXsaction : LogpWriteLargeRecordData failed. Lsn=0x%1!08lx!\r\n", Lsn); Lsn = NULL_LSN; } } FnExit: #if DBG { DWORD dwOldProtect; DWORD Status; BOOL VPWorked; VPWorked = VirtualProtect(pLog->ActivePage, pLog->SectorSize, PAGE_READONLY, & dwOldProtect); Status = GetLastError(); CL_ASSERT( VPWorked ); } #endif if (dwError != ERROR_SUCCESS) SetLastError(dwError); LeaveCriticalSection(&pLog->Lock); ClRtlLogPrint(LOG_NOISE, "[LM] LogWriteXsaction : Exit returning=0x%1!08lx!\r\n", Lsn); return(Lsn); } /**** @func DWORD | LogCommitXsaction | This writes the commit transaction record to the log and flushes it. @parm IN HLOG | hLog | Supplies the handle to the log. @parm IN TRID | TrId | Supplies the transaction id. @parm IN RMID | ResourceId | The resource id that identifies the resource manager. @parm IN RMTYPE | ResourceFlags | A dword of flags that the resource manager may use to store any data it wants with this record. @comm A commit record is written to the quorum log. The hXsaction handle is invalidated at this point and should not be used after this call has been made. The commit is record is used to identify committed transactions during rollback. @rdesc ERROR_SUCCESS if successful. Win32 error code if something horrible happened. @xref ****/ DWORD WINAPI LogCommitXsaction( IN HLOG hLog, IN HXSACTION hXsaction, IN RMTYPE ResourceFlags) { DWORD dwError = ERROR_SUCCESS; LSN Lsn; PXSACTION pXsaction; ClRtlLogPrint(LOG_NOISE, "[LM] LogCommitXsaction : Entry, hXsaction=0x%1!08lx!\r\n", hXsaction); GETXSACTION(pXsaction, hXsaction); Lsn = LogWrite(hLog, pXsaction->TrId, TTCommitXsaction, pXsaction->RmId, ResourceFlags, NULL, 0); if (Lsn == NULL_LSN) { dwError = GetLastError(); goto FnExit; } FnExit: //free up the transation memory ZeroMemory(pXsaction, sizeof(XSACTION)); // just in case somebody tries to LocalFree(pXsaction); ClRtlLogPrint(LOG_NOISE, "[LM] LogCommitXsaction : Exit, dwError=0x%1!08lx!\r\n", dwError); return(dwError); } /**** @func DWORD | LogAbortXsaction | Marks a given transaction as aborted in the quorum log file. @parm IN HLOG | hLog | Supplies the handle to the log. @parm IN HXSACTION | hXsaction | Supplies the handle to the transaction. @parm IN RMTYPE | ResourceFlags | A dword of flags that the resource manager may use to store any data it wants with this record. @comm An abort transaction is written to the quorum log. This is used in identifying aborted transactions during roll back. The hXsaction handle is invalidated at this point and should not be used after this. @rdesc ERROR_SUCCESS if successful. Win32 error code if something horrible happened. @xref ****/ DWORD LogAbortXsaction( IN HLOG hLog, IN HXSACTION hXsaction, IN RMTYPE ResourceFlags ) { PXSACTION pXsaction; LSN Lsn; DWORD dwError = ERROR_SUCCESS; ClRtlLogPrint(LOG_NOISE, "[LM] LogAbortXsaction : Entry, hXsaction=0x%1!08lx!\r\n", hXsaction); GETXSACTION(pXsaction, hXsaction); Lsn = LogWrite(hLog, pXsaction->TrId, TTAbortXsaction, pXsaction->RmId, ResourceFlags, NULL, 0); if (Lsn == NULL_LSN) { dwError = GetLastError(); goto FnExit; } FnExit: ZeroMemory(pXsaction, sizeof(XSACTION)); // just in case somebody tries to LocalFree(pXsaction); ClRtlLogPrint(LOG_NOISE, "[LM] LogAbortXsaction : Exit, returning, dwError=0x%1!08lx!\r\n", dwError); return(dwError); } /**** @func LSN | LogFindXsactionState | This fuctions scans the record and finds the state of a given transaction. @parm IN HLOG | hLog | Supplies the identifier of the log. @parm IN LSN | StartXsactionLsn | The LSN of the start transaction record. @parm IN TRID | XsactionId | The transaction id of the transaction. @parm OUT TRSTATE | *pXsactionState | The state of the transaction. @comm Transaction state is set to XsactionCommitted, XsactionAborted or XsactionUnknown. depending on whether a commit record, or abort record or no record is found for this record in the log. @rdesc ERROR_SUCCESS, else returns the error code if something horrible happens. @xref ****/ DWORD LogFindXsactionState( IN HLOG hLog, IN LSN StartXsactionLsn, IN TRID XsactionId, OUT TRSTATE *pXsactionState) { PLOG pLog; PLOGRECORD pRecord, pEopRecord; DWORD dwError = ERROR_SUCCESS; int PageIndex, OldPageIndex; RMID Resource; TRID TrId; TRTYPE TrType; LSN Lsn, EopLsn; PLOGPAGE pPage = NULL,pLargeBuffer = NULL; DWORD dwBytesRead; RMTYPE ResourceFlags; BOOL bFound = FALSE; GETLOG(pLog, hLog); ClRtlLogPrint(LOG_NOISE, "[LM] LogWrite : Entry StartXLsn=0x%1!08lx! StartXId=%2!u!\r\n", StartXsactionLsn, XsactionId); EnterCriticalSection(&pLog->Lock); if (StartXsactionLsn >= pLog->NextLsn) { dwError = ERROR_INVALID_PARAMETER; goto FnExit; } //read this record dwBytesRead = 0; if ((Lsn = LogRead( hLog, StartXsactionLsn, &Resource, &ResourceFlags, &TrId, &TrType, NULL, &dwBytesRead)) == NULL_LSN) { dwError = GetLastError(); goto FnExit; } //check the record if ((TrType != TTStartXsaction) || (TrId != XsactionId)) { dwError = ERROR_INVALID_PARAMETER; goto FnExit; } pPage = (PLOGPAGE)AlignAlloc(SECTOR_SIZE); if (pPage == NULL) { CL_UNEXPECTED_ERROR( ERROR_NOT_ENOUGH_MEMORY ); } //Lsn is now set to the next Lsn after the start //initialize this to -1 so that the first page is always read OldPageIndex = -1; while (Lsn < pLog->NextLsn && !bFound) { // // Scan From Next record to find either the commit or abort record // PageIndex = LSNTOPAGE(Lsn); if (PageIndex != OldPageIndex) { //read the page pLog->Overlapped.Offset = PageIndex * pLog->SectorSize; pLog->Overlapped.OffsetHigh = 0; dwError = LogpRead(pLog, pPage, pLog->SectorSize, &dwBytesRead); if (dwError) { goto FnExit; } //read was successful, no need to read the page unless the //record falls on a different page OldPageIndex = PageIndex; } pRecord = LSNTORECORD(pPage, Lsn); //skip other log management records //these are small records by definition if (pRecord->ResourceManager < RMAny) { Lsn = GETNEXTLSN(pRecord, TRUE); continue; } //if the transaction id is the same, check the xsaction type if (pRecord->Transaction == XsactionId) { if ((pRecord->XsactionType == TTCommitXsaction) || (pRecord->XsactionType == TTStartXsaction)) { bFound = TRUE; continue; } } //handle large records if (pRecord->NumPages > 0) { EopLsn = GETNEXTLSN(pRecord,TRUE); PageIndex = LSNTOPAGE(EopLsn); //read the page pLog->Overlapped.Offset = PageIndex * pLog->SectorSize; pLog->Overlapped.OffsetHigh = 0; dwError = LogpRead(pLog, pPage, pLog->SectorSize, &dwBytesRead); if (dwError) { goto FnExit; } OldPageIndex = PageIndex; pEopRecord = (PLOGRECORD)((ULONG_PTR) pPage + (EopLsn - (pLog->Overlapped).Offset)); //move to the next page Lsn = GETNEXTLSN(pEopRecord, TRUE); } else { Lsn = GETNEXTLSN(pRecord, TRUE); } } if (bFound) { if (pRecord->XsactionType == TTCommitXsaction) *pXsactionState = XsactionCommitted; else *pXsactionState = XsactionAborted; } else { *pXsactionState = XsactionUnknown; } ClRtlLogPrint(LOG_NOISE, "[LM] LogFindXsactionState : Exit,State=%1!u!\r\n", *pXsactionState); FnExit: LeaveCriticalSection(&pLog->Lock); if (pPage) AlignFree(pPage); return(dwError); } /**** @func LSN | LogScanXsaction | This fuctions scans the multiple units of a transaction. @parm IN HLOG | hLog | Supplies the identifier of the log. @parm IN LSN | StartXsacionLsn | The LSN of the start transaction record. @parm IN TRID | XsactionId | The transaction id of the transaction. @parm IN PLOG_SCANXSACTION_CALLBACK | CallbackRoutine | The routine to call for every unit of a transaction. @parm IN PVOID | pContext | The context to be passed to state of the transaction. @comm Stops enumerating the transaction units if the callback function returns FALSE, or if the abort or commit record for this transaction is found or if the next transacion is found. @rdesc ERROR_SUCCESS if the state is found, else returns the error code. @xref ****/ DWORD LogScanXsaction( IN HLOG hLog, IN LSN StartXsactionLsn, IN TRID XsactionId, IN PLOG_SCANXSACTION_CALLBACK CallbackRoutine, IN PVOID pContext) { PLOG pLog; PLOGRECORD pRecord; DWORD dwError = ERROR_SUCCESS; int PageIndex, OldPageIndex; RMID Resource; TRID TrId; TRTYPE TrType; LSN Lsn; PLOGPAGE pPage = NULL; PUCHAR pLargeBuffer; DWORD dwBytesRead; RMTYPE ResourceFlags; GETLOG(pLog, hLog); ClRtlLogPrint(LOG_NOISE, "[LM] LogScanXsaction : Entry StartXLsn=0x%1!08lx! StartXId=%2!u!\r\n", StartXsactionLsn, XsactionId); Lsn = StartXsactionLsn; if (Lsn >= pLog->NextLsn) { dwError = ERROR_INVALID_PARAMETER; goto FnExit; } //read this record dwBytesRead = 0; if (LogRead( hLog, Lsn, &Resource, &ResourceFlags, &TrId, &TrType, NULL, &dwBytesRead) == NULL_LSN) { dwError = GetLastError(); goto FnExit; } //check the record if ((TrType != TTStartXsaction) || (TrId != XsactionId)) { dwError = ERROR_INVALID_PARAMETER; goto FnExit; } pPage = (PLOGPAGE)AlignAlloc(SECTOR_SIZE); if (pPage == NULL) { CL_UNEXPECTED_ERROR( ERROR_NOT_ENOUGH_MEMORY ); } //initialize this to -1 so that the first page is always read OldPageIndex = -1; while (Lsn < pLog->NextLsn) { // // Scan From Next record to find either the commit or abort record // PageIndex = LSNTOPAGE(Lsn); if (PageIndex != OldPageIndex) { //read the page pLog->Overlapped.Offset = PageIndex * pLog->SectorSize; pLog->Overlapped.OffsetHigh = 0; dwError = LogpRead(pLog, pPage, pLog->SectorSize, &dwBytesRead); if (dwError) { goto FnExit; } //read was successful, no need to read the page unless the //record falls on a different page OldPageIndex = PageIndex; } pRecord = LSNTORECORD(pPage, Lsn); //skip other log management records if (pRecord->ResourceManager < RMAny) { Lsn = GETNEXTLSN(pRecord, TRUE); continue; } //stop if next transaction record is encountered if (pRecord->Transaction > XsactionId) { break; } //stop when a commit or abort record is found if ((pRecord->Transaction == XsactionId) && ((pRecord->XsactionType == TTCommitXsaction) || (pRecord->XsactionType == TTAbortXsaction))) { break; } //handle large records if (pRecord->NumPages > 0) { //if the transaction id is the same if ((pRecord->Transaction == XsactionId) && (pRecord->XsactionType == TTXsactionUnit)) { //read the whole record //for a large record you need to read in the entire data pLargeBuffer = AlignAlloc(pRecord->NumPages * SECTOR_SIZE); if (pLargeBuffer == NULL) { dwError = ERROR_NOT_ENOUGH_MEMORY ; CL_LOGFAILURE(ERROR_NOT_ENOUGH_MEMORY); break; } //read the pages pLog->Overlapped.Offset = PageIndex * pLog->SectorSize; pLog->Overlapped.OffsetHigh = 0; dwError = LogpRead(pLog, pLargeBuffer, pRecord->NumPages * pLog->SectorSize, &dwBytesRead); //if it is the last page, then set the new page as the active //page if (dwError != ERROR_SUCCESS) { CL_LOGFAILURE(dwError); AlignFree(pLargeBuffer); break; } pRecord = LSNTORECORD((PLOGPAGE)pLargeBuffer, Lsn); ClRtlLogPrint(LOG_NOISE, "[LM] LogScanXsaction::Calling the scancb for Lsn=0x%1!08lx! Trid=%2!u! RecordSize=%3!u!\r\n", Lsn, pRecord->Transaction, pRecord->DataSize); //if the callback requests to stop scan if (!(*CallbackRoutine)(pContext, Lsn, pRecord->ResourceManager, pRecord->Flags, pRecord->Transaction, pRecord->Data, pRecord->DataSize)) { AlignFree(pLargeBuffer); break; } } //read the last page of the large record and advance Lsn = GETNEXTLSN(pRecord,TRUE); AlignFree(pLargeBuffer); PageIndex = LSNTOPAGE(Lsn); //read the page pLog->Overlapped.Offset = PageIndex * pLog->SectorSize; pLog->Overlapped.OffsetHigh = 0; dwError = LogpRead(pLog, pPage, pLog->SectorSize, &dwBytesRead); if (dwError) { goto FnExit; } OldPageIndex = PageIndex; pRecord = (PLOGRECORD)((ULONG_PTR) pPage + (Lsn - (pLog->Overlapped).Offset)); CL_ASSERT(pRecord->ResourceManager == RMPageEnd); //move to the next page Lsn = GETNEXTLSN(pRecord, TRUE); } else { if ((pRecord->Transaction == XsactionId) && (pRecord->XsactionType == TTXsactionUnit)) { ClRtlLogPrint(LOG_NOISE, "[LM] LogScanXsaction: Calling the scancb for Lsn=0x%1!08lx! Trid=%2!u! RecordSize=%3!u!\r\n", Lsn, pRecord->Transaction, pRecord->DataSize); //call the callback if (!(*CallbackRoutine)(pContext, Lsn, pRecord->ResourceManager, pRecord->Flags, pRecord->Transaction, pRecord->Data, pRecord->DataSize)) { break; } } Lsn = GETNEXTLSN(pRecord, TRUE); } } FnExit: if (pPage) AlignFree(pPage); return(dwError); }