public static LfsRestartPage Create(long fileSize, int bytesPerSystemPage, int bytesPerLogPage, params LfsClientRecord[] clients) { LfsRestartPage restartPage = new LfsRestartPage(); restartPage.LogPageSize = (uint)bytesPerLogPage; restartPage.RestartArea.CurrentLsn = 0; restartPage.RestartArea.LastLsnDataLength = 0; restartPage.RestartArea.ClientFreeList = LfsRestartArea.NoClient; restartPage.RestartArea.ClientInUseList = (clients.Length > 0) ? (ushort)0 : LfsRestartArea.NoClient; restartPage.RestartArea.Flags = LfsRestartFlags.CleanDismount; restartPage.RestartArea.FileSize = (ulong)fileSize; restartPage.RestartArea.FileSizeBits = LfsRestartArea.CalculateFileSizeBits(fileSize); restartPage.RestartArea.LogPageDataOffset = (ushort)LfsRecordPage.GetDataOffset(bytesPerLogPage); if (clients.Length > 0) { clients[0].PrevClient = LfsRestartArea.NoClient; clients[clients.Length - 1].NextClient = LfsRestartArea.NoClient; } for (int index = 1; index < clients.Length; index++) { clients[index].PrevClient = (ushort)(index - 1); } restartPage.RestartArea.LogClientArray.AddRange(clients); return(restartPage); }
private void WritePage(ulong pageOffset, LfsRecordPage page) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } byte[] pageBytes = page.GetBytes((int)m_restartPage.LogPageSize, true); WriteData(pageOffset, pageBytes); }
private void WriteTailCopy(ulong pageOffset, LfsRecordPage page) { 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; ulong lastLsn = page.LastLsnOrFileOffset; page.LastLsnOrFileOffset = pageOffset; WritePage(tailPageOffset, page); // Restore the page to its original state page.LastLsnOrFileOffset = lastLsn; }
public LfsRecord ReadRecord(ulong lsn) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } ulong pageOffsetInFile = LsnToPageOffsetInFile(lsn); int recordOffsetInPage = LsnToRecordOffsetInPage(lsn); LfsRecordPage page = ReadPage(pageOffsetInFile); if (page == null) { throw new InvalidDataException("LfsRecord LSN points to an uninitialized page"); } int dataOffset = m_restartPage.RestartArea.LogPageDataOffset; LfsRecord record = page.ReadRecord(recordOffsetInPage); if (record.ThisLsn != lsn) { throw new InvalidDataException("LfsRecord LSN does not match expected value"); } if (record.Length < LfsRecord.HeaderLength) { throw new InvalidDataException("LfsRecord length is invalid"); } if (record.IsMultiPageRecord) { int recordLength = (int)(LfsRecord.HeaderLength + record.ClientDataLength); int bytesRemaining = recordLength - (LfsRecord.HeaderLength + record.Data.Length); while (bytesRemaining > 0) { pageOffsetInFile += m_restartPage.LogPageSize; if (pageOffsetInFile == m_restartPage.RestartArea.FileSize) { pageOffsetInFile = m_restartPage.SystemPageSize * 2 + m_restartPage.LogPageSize * 2; } page = ReadPage(pageOffsetInFile); int bytesToRead = Math.Min((int)m_restartPage.LogPageSize - dataOffset, bytesRemaining); record.Data = ByteUtils.Concatenate(record.Data, page.ReadBytes(dataOffset, bytesToRead)); bytesRemaining -= bytesToRead; } } return(record); }
/// <summary> /// This method will repair the log file by copying the tail copies back to their correct location. /// If necessary, the restart area will be updated to reflect CurrentLsn and LastLsnDataLength. /// </summary> /// <remarks>This method will only repair the log file, further actions are needed to bring the volume back to a consistent state.</remarks> private void RepairLogFile() { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } // Note: this implementation is not as exhaustive as it should be. LfsRecordPage firstTailPage = null; LfsRecordPage secondTailPage = null; try { firstTailPage = ReadPageFromFile(m_restartPage.SystemPageSize * 2); } catch (InvalidDataException) { } try { secondTailPage = ReadPageFromFile(m_restartPage.SystemPageSize * 2 + m_restartPage.LogPageSize); } catch (InvalidDataException) { } // Find the most recent tail copy LfsRecordPage tailPage = null; if (firstTailPage != null) { tailPage = firstTailPage; } if (tailPage == null || (secondTailPage != null && secondTailPage.LastEndLsn > firstTailPage.LastEndLsn)) { tailPage = secondTailPage; } if (tailPage != null) { LfsRecordPage page = null; try { page = ReadPageFromFile(tailPage.LastLsnOrFileOffset); } catch (InvalidDataException) { } if (page == null || tailPage.LastEndLsn > page.LastLsnOrFileOffset) { ulong pageOffsetInFile = tailPage.LastLsnOrFileOffset; tailPage.LastLsnOrFileOffset = tailPage.LastEndLsn; WritePage(pageOffsetInFile, tailPage); if (tailPage.LastEndLsn > m_restartPage.LogRestartArea.CurrentLsn) { m_restartPage.LogRestartArea.CurrentLsn = tailPage.LastEndLsn; int recordOffsetInPage = LsnToRecordOffsetInPage(tailPage.LastEndLsn); LfsRecord record = tailPage.ReadRecord(recordOffsetInPage); m_restartPage.LogRestartArea.LastLsnDataLength = (uint)record.Data.Length; WriteRestartPage(m_restartPage); } } } }
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; } }