private void WriteRecord(LfsRecord record) { ulong pageOffsetInFile = LsnToPageOffsetInFile(record.ThisLsn); int recordOffsetInPage = LsnToRecordOffsetInPage(record.ThisLsn); bool initializeNewPage = (recordOffsetInPage == m_restartPage.LogRestartArea.LogPageDataOffset); LfsRecordPage page; if (initializeNewPage) // We write the record at the beginning of a new page, we must initialize the page { // When the NTFS v5.1 driver restarts a dirty log file, it does not expect to find more than one transfer after the last flushed LSN. // If more than one transfer is encountered, it is treated as a fatal error and the driver will report STATUS_DISK_CORRUPT_ERROR. // Updating the CurrentLsn / LastLsnDataLength in the restart area will make the NTFS driver see the new page as the only transfer after the flushed LCN. WriteRestartPage(false); page = new LfsRecordPage((int)m_restartPage.LogPageSize, m_restartPage.LogRestartArea.LogPageDataOffset); page.LastLsnOrFileOffset = 0; page.LastEndLsn = 0; page.NextRecordOffset = m_restartPage.LogRestartArea.LogPageDataOffset; page.UpdateSequenceNumber = m_nextUpdateSequenceNumber; // There is no rule governing the value of the USN m_nextUpdateSequenceNumber++; } else { page = ReadPage(pageOffsetInFile); } int bytesAvailableInFirstPage = (int)m_restartPage.LogPageSize - recordOffsetInPage; int bytesToWrite = Math.Min(record.Length, bytesAvailableInFirstPage); int bytesRemaining = record.Length - bytesToWrite; record.IsMultiPageRecord = (bytesRemaining > 0); byte[] recordBytes = record.GetBytes(); page.WriteBytes(recordOffsetInPage, recordBytes, bytesToWrite); int bytesAvailableInPage = (int)m_restartPage.LogPageSize - (int)m_restartPage.LogRestartArea.LogPageDataOffset; int pageCount = 1 + (int)Math.Ceiling((double)bytesRemaining / bytesAvailableInPage); page.PageCount = (ushort)pageCount; page.PagePosition = 1; if (!record.IsMultiPageRecord && (recordOffsetInPage + bytesToWrite > page.NextRecordOffset)) { page.NextRecordOffset = (ushort)(recordOffsetInPage + bytesToWrite); } ulong firstTailPageOffset = m_restartPage.SystemPageSize * 2; ulong secondTailPageOffset = m_restartPage.SystemPageSize * 2 + m_restartPage.LogPageSize; ulong tailPageOffset = m_isFirstTailPageTurn ? firstTailPageOffset : secondTailPageOffset; m_isFirstTailPageTurn = !m_isFirstTailPageTurn; if (record.ThisLsn > page.LastLsnOrFileOffset) { page.LastLsnOrFileOffset = record.ThisLsn; } if (!record.IsMultiPageRecord && record.ThisLsn > page.LastEndLsn) { page.LastEndLsn = record.ThisLsn; page.HasRecordEnd = true; } // Write tail copy ulong lastPageLsn = page.LastLsnOrFileOffset; page.LastLsnOrFileOffset = pageOffsetInFile; WritePage(tailPageOffset, page); page.LastLsnOrFileOffset = lastPageLsn; // Write page WritePage(pageOffsetInFile, page); int pagePosition = 2; while (bytesRemaining > 0) { pageOffsetInFile += m_restartPage.LogPageSize; if (pageOffsetInFile == m_restartPage.LogRestartArea.FileSize) { pageOffsetInFile = m_restartPage.SystemPageSize * 2 + m_restartPage.LogPageSize * 2; } bytesToWrite = Math.Min(bytesRemaining, bytesAvailableInPage); LfsRecordPage nextPage = new LfsRecordPage((int)m_restartPage.LogPageSize, m_restartPage.LogRestartArea.LogPageDataOffset); Array.Copy(recordBytes, recordBytes.Length - bytesRemaining, nextPage.Data, 0, bytesToWrite); bytesRemaining -= bytesToWrite; nextPage.LastLsnOrFileOffset = record.ThisLsn; if (bytesRemaining == 0) { nextPage.LastEndLsn = record.ThisLsn; nextPage.Flags |= LfsRecordPageFlags.RecordEnd; } nextPage.PageCount = (ushort)pageCount; nextPage.PagePosition = (ushort)pagePosition; nextPage.UpdateSequenceNumber = m_nextUpdateSequenceNumber; // There is no rule governing the value of the USN m_nextUpdateSequenceNumber++; WritePage(pageOffsetInFile, nextPage); pagePosition++; } }
/// <param name="endOfTransferRecorded">True if one or more pages has been filled and the next transfer will be seen as a separate IO transfer</param> private void FlushRecords(List <LfsRecord> records, out bool endOfTransferRecorded) { KeyValuePairList <ulong, LfsRecordPage> pagesToWrite = new KeyValuePairList <ulong, LfsRecordPage>(); int bytesAvailableInPage = (int)m_restartPage.LogPageSize - (int)m_restartPage.RestartArea.LogPageDataOffset; LfsRecordPage currentPage = null; bool overwriteTailPage = false; for (int recordIndex = 0; recordIndex < records.Count; recordIndex++) { LfsRecord record = records[recordIndex]; ulong pageOffsetInFile = LsnToPageOffsetInFile(record.ThisLsn); int recordOffsetInPage = LsnToRecordOffsetInPage(record.ThisLsn); bool initializeNewPage = (recordOffsetInPage == m_restartPage.RestartArea.LogPageDataOffset); if (initializeNewPage) // We write the record at the beginning of a new page, we must initialize the page { currentPage = new LfsRecordPage((int)m_restartPage.LogPageSize, m_restartPage.RestartArea.LogPageDataOffset); currentPage.LastLsnOrFileOffset = 0; currentPage.LastEndLsn = 0; currentPage.NextRecordOffset = m_restartPage.RestartArea.LogPageDataOffset; currentPage.UpdateSequenceNumber = m_nextUpdateSequenceNumber; // There is no rule governing the value of the USN m_nextUpdateSequenceNumber++; pagesToWrite.Add(pageOffsetInFile, currentPage); } else { if (recordIndex == 0) // currentPage == null { if (m_tailPage == null) { m_tailPage = ReadPage(pageOffsetInFile);; m_tailPageOffsetInFile = pageOffsetInFile; } else if (m_tailPageOffsetInFile != pageOffsetInFile) { throw new InvalidOperationException("LFS log records must be written sequentially"); } currentPage = m_tailPage; overwriteTailPage = true; pagesToWrite.Add(pageOffsetInFile, currentPage); } // currentPage cannot be null when !initializeNewPage && recordIndex > 0 System.Diagnostics.Debug.Assert(currentPage != null); } int bytesAvailableInFirstPage = (int)m_restartPage.LogPageSize - recordOffsetInPage; int bytesToWrite = Math.Min(record.Length, bytesAvailableInFirstPage); int bytesRemaining = record.Length - bytesToWrite; record.IsMultiPageRecord = (bytesRemaining > 0); byte[] recordBytes = record.GetBytes(); currentPage.WriteBytes(recordOffsetInPage, recordBytes, bytesToWrite); currentPage.LastLsnOrFileOffset = record.ThisLsn; if (!record.IsMultiPageRecord) { currentPage.NextRecordOffset = (ushort)(recordOffsetInPage + bytesToWrite); currentPage.LastEndLsn = record.ThisLsn; currentPage.HasRecordEnd = true; } bool reusePage = !record.IsMultiPageRecord && (bytesAvailableInFirstPage >= m_restartPage.RestartArea.RecordHeaderLength); if (!reusePage) { currentPage = null; } while (bytesRemaining > 0) { pageOffsetInFile += m_restartPage.LogPageSize; if (pageOffsetInFile == m_restartPage.RestartArea.FileSize) { pageOffsetInFile = m_restartPage.SystemPageSize * 2 + m_restartPage.LogPageSize * 2; } bytesToWrite = Math.Min(bytesRemaining, bytesAvailableInPage); LfsRecordPage nextPage = new LfsRecordPage((int)m_restartPage.LogPageSize, m_restartPage.RestartArea.LogPageDataOffset); Array.Copy(recordBytes, recordBytes.Length - bytesRemaining, nextPage.Data, 0, bytesToWrite); bytesRemaining -= bytesToWrite; nextPage.LastLsnOrFileOffset = record.ThisLsn; if (bytesRemaining == 0) { nextPage.LastEndLsn = record.ThisLsn; nextPage.NextRecordOffset = (ushort)(m_restartPage.RestartArea.LogPageDataOffset + bytesToWrite); nextPage.Flags |= LfsRecordPageFlags.RecordEnd; int bytesAvailableInLastPage = (int)m_restartPage.LogPageSize - ((int)m_restartPage.RestartArea.LogPageDataOffset + bytesToWrite); bool reuseLastPage = (bytesAvailableInLastPage >= m_restartPage.RestartArea.RecordHeaderLength); if (reuseLastPage) { currentPage = nextPage; } } nextPage.UpdateSequenceNumber = m_nextUpdateSequenceNumber; // There is no rule governing the value of the USN m_nextUpdateSequenceNumber++; pagesToWrite.Add(pageOffsetInFile, nextPage); } } // The tail copy is used to avoid losing both the records from the previous IO transfer and the current one if the page becomes corrupted due to a power failue. endOfTransferRecorded = (pagesToWrite.Count > 1 || currentPage == null); if (!endOfTransferRecorded) { ulong pageOffsetInFile = pagesToWrite[0].Key; if (m_isTailCopyDirty || !overwriteTailPage) { WritePage(pageOffsetInFile, currentPage); m_isTailCopyDirty = false; } else { WriteTailCopy(pageOffsetInFile, currentPage);; m_isTailCopyDirty = true; } } else { for (int pageIndex = 0; pageIndex < pagesToWrite.Count; pageIndex++) { ulong pageOffsetInFile = pagesToWrite[pageIndex].Key; LfsRecordPage page = pagesToWrite[pageIndex].Value; page.PagePosition = (ushort)(pageIndex + 1); page.PageCount = (ushort)pagesToWrite.Count; if (pageIndex == 0 && !m_isTailCopyDirty && overwriteTailPage) { // We are about to overwrite a page from an older transfer that does not have a tail copy, create a tail copy WriteTailCopy(pageOffsetInFile, page); } WritePage(pageOffsetInFile, page); } } if (currentPage == null) { m_tailPage = null; m_isTailCopyDirty = false; } else { m_tailPage = currentPage; m_tailPageOffsetInFile = pagesToWrite[pagesToWrite.Count - 1].Key; } }