private LfsRecord WriteRestartRecord(ulong usnJournalUnknown1, ulong previousRestartRecordLsn, MftSegmentReference usnJournal, ulong usnJournalUnknown2, bool isClean) { NTFSRestartRecord restartRecord = new NTFSRestartRecord(m_majorVersion, m_minorVersion); restartRecord.StartOfCheckpointLsn = m_lastClientLsn; restartRecord.UsnJournalUnknown1 = usnJournalUnknown1; restartRecord.PreviousRestartRecordLsn = previousRestartRecordLsn; restartRecord.BytesPerCluster = (uint)Volume.BytesPerCluster; restartRecord.UsnJournal = usnJournal; restartRecord.UsnJournalUnknown2 = usnJournalUnknown2; if (isClean) { if (m_transactions.Count > 0) { throw new InvalidOperationException("All TransactionIDs must be deallocated before writing a clean restart record"); } } else { foreach (Transaction transaction in m_transactions) { if (transaction.OldestLsn != 0 && transaction.OldestLsn < restartRecord.StartOfCheckpointLsn) { restartRecord.StartOfCheckpointLsn = transaction.OldestLsn; } } if (m_openAttributes.Count > 0) { byte[] attributeNameTableBytes; byte[] openAttributeTableBytes = GetOpenAttributeTableBytes(out attributeNameTableBytes); m_lastClientLsn = 0; uint transactionID = AllocateTransactionID(); // These records must have a valid transactionID LfsRecord openAttributeTableRecord = WriteLogRecord(null, null, 0, 0, 0, 0, NTFSLogOperation.OpenAttributeTableDump, openAttributeTableBytes, NTFSLogOperation.Noop, new byte[0], transactionID, false); restartRecord.OpenAttributeTableLsn = openAttributeTableRecord.ThisLsn; restartRecord.OpenAttributeTableLength = (uint)openAttributeTableBytes.Length; if (attributeNameTableBytes != null) { LfsRecord attributeNameTableRecord = WriteLogRecord(null, null, 0, 0, 0, 0, NTFSLogOperation.AttributeNamesDump, attributeNameTableBytes, NTFSLogOperation.Noop, new byte[0], transactionID, false); restartRecord.AttributeNamesLsn = attributeNameTableRecord.ThisLsn; restartRecord.AttributeNamesLength = (uint)attributeNameTableBytes.Length; } DeallocateTransactionID(transactionID); } } byte[] clientData = restartRecord.GetBytes(Volume.MajorVersion); LfsRecord result = m_logFile.WriteRecord(m_clientIndex, LfsRecordType.ClientRestart, 0, 0, 0, clientData, true); m_lastClientLsn = result.ThisLsn; LfsClientRecord clientRecord = m_logFile.GetClientRecord(m_clientIndex); clientRecord.OldestLsn = restartRecord.StartOfCheckpointLsn; clientRecord.ClientRestartLsn = result.ThisLsn; // Note that writing a client restart record without also updating ClientRestartLsn has no effect. // During the analysis pass, the NTFS v5.1 driver will use ClientRestartLsn in the most recent restart page to determine // the StartOfCheckpointLsn that the scan should start from, any client restart record that is found during the scan will be ignored. m_logFile.WriteRestartPage(isClean); m_currentRestartRecord = restartRecord; return(result); }
public LfsRecord FindNextRecord(LfsRecord record, int clientIndex) { do { ulong nextLsn = CalculateNextLsn(record.ThisLsn, record.Length); if (!IsLsnInFile(nextLsn, clientIndex)) { return(null); } try { record = ReadRecord(nextLsn); } catch { return(null); } ushort clientSeqNumber = m_restartPage.LogRestartArea.LogClientArray[clientIndex].SeqNumber; if (record.ClientIndex == clientIndex && record.ClientSeqNumber == clientSeqNumber) { return(record); } }while (true); }
public void WriteRestartRecord(ushort majorNTFSVersion, bool isClean) { NTFSRestartRecord previousRestartRecord = ReadCurrentRestartRecord(); MftSegmentReference usnJournal = previousRestartRecord.UsnJournal; ulong previousRestartRecordLsn = previousRestartRecord.PreviousRestartRecordLsn; LfsRecord restartRecord = WriteRestartRecord(previousRestartRecordLsn, usnJournal, majorNTFSVersion, isClean); }
public void WriteRestartRecord(bool isClean) { ulong usnJournalUnknown1 = m_currentRestartRecord.UsnJournalUnknown1; ulong previousRestartRecordLsn = m_currentRestartRecord.PreviousRestartRecordLsn; MftSegmentReference usnJournal = m_currentRestartRecord.UsnJournal; ulong usnJournalUnknown2 = m_currentRestartRecord.UsnJournalUnknown2; LfsRecord restartRecord = WriteRestartRecord(usnJournalUnknown1, previousRestartRecordLsn, usnJournal, usnJournalUnknown2, isClean); }
private LfsRecord WriteRestartRecord(ulong previousRestartRecordLsn, MftSegmentReference usnJournal, ushort majorNTFSVersion, bool isClean) { NTFSRestartRecord restartRecord = new NTFSRestartRecord(m_majorVersion, m_minorVersion); restartRecord.StartOfCheckpointLsn = m_lastClientLsn; restartRecord.PreviousRestartRecordLsn = previousRestartRecordLsn; if (isClean) { if (m_transactions.Count > 0) { throw new InvalidOperationException("All TransactionIDs must be deallocated before writing a clean restart record"); } } else if (m_openAttributes.Count > 0) { byte[] attributeNameTableBytes; byte[] openAttributeTableBytes = GetOpenAttributeTableBytes(out attributeNameTableBytes); m_lastClientLsn = 0; uint transactionID = AllocateTransactionID(); // These records must have a valid transactionID LfsRecord openAttributeTableRecord = WriteLogRecord(null, null, 0, NTFSLogOperation.OpenAttributeTableDump, openAttributeTableBytes, NTFSLogOperation.Noop, new byte[0], transactionID); restartRecord.OpenAttributeTableLsn = openAttributeTableRecord.ThisLsn; restartRecord.OpenAttributeTableLength = (uint)openAttributeTableBytes.Length; if (attributeNameTableBytes != null) { LfsRecord attributeNameTableRecord = WriteLogRecord(null, null, 0, NTFSLogOperation.AttributeNamesDump, openAttributeTableBytes, NTFSLogOperation.Noop, new byte[0], transactionID); restartRecord.AttributeNamesLsn = attributeNameTableRecord.ThisLsn; restartRecord.AttributeNamesLength = (uint)attributeNameTableBytes.Length; } DeallocateTransactionID(transactionID); } restartRecord.BytesPerCluster = (uint)Volume.BytesPerCluster; restartRecord.UsnJournal = usnJournal; byte[] clientData = restartRecord.GetBytes(majorNTFSVersion); LfsRecord result = m_logFile.WriteRecord(m_clientIndex, LfsRecordType.ClientRestart, 0, 0, 0, clientData); m_lastClientLsn = result.ThisLsn; LfsClientRecord clientRecord = m_logFile.GetClientRecord(m_clientIndex); if (isClean) { clientRecord.OldestLsn = restartRecord.StartOfCheckpointLsn; } else { ulong oldestLsn = restartRecord.StartOfCheckpointLsn; foreach (Transaction transaction in m_transactions) { if (transaction.OldestLsn != 0 && transaction.OldestLsn < oldestLsn) { oldestLsn = transaction.OldestLsn; } } clientRecord.OldestLsn = oldestLsn; } clientRecord.ClientRestartLsn = result.ThisLsn; m_logFile.WriteRestartPage(isClean); return(result); }
public LfsRecord WriteLogRecord(MftSegmentReference fileReference, AttributeRecord attributeRecord, ulong streamOffset, int structureLength, int recordOffset, int attributeOffset, NTFSLogOperation redoOperation, byte[] redoData, NTFSLogOperation undoOperation, byte[] undoData, uint transactionID, bool flushToDisk) { int openAttributeOffset = 0; if (fileReference != null) { int openAttributeIndex = IndexOfOpenAttribute(fileReference, attributeRecord.AttributeType, attributeRecord.Name); if (openAttributeIndex == -1) { openAttributeIndex = AddToOpenAttributeTable(fileReference, attributeRecord.AttributeType, attributeRecord.Name, m_lastClientLsn); m_openAttributes[openAttributeIndex].AssociatedTransactions.Add(transactionID); openAttributeOffset = OpenAttributeIndexToOffset(openAttributeIndex); OpenAttributeEntry entry = new OpenAttributeEntry(m_majorVersion); entry.AllocatedOrNextFree = RestartTableEntry.RestartEntryAllocated; // Note: NTFS v5.1 driver calulates AttributeOffset using entry length of 0x28, the reason is unclear but we're immitating this. entry.AttributeOffset = (uint)(RestartTableHeader.Length + openAttributeIndex * OpenAttributeEntry.LengthV1); entry.FileReference = fileReference; entry.LsnOfOpenRecord = m_lastClientLsn; entry.AttributeTypeCode = attributeRecord.AttributeType; if (attributeRecord.AttributeType == AttributeType.IndexAllocation) { entry.BytesPerIndexBuffer = (uint)Volume.BytesPerIndexRecord; } byte[] openAttributeBytes = entry.GetBytes(); byte[] attributeNameBytes = System.Text.Encoding.Unicode.GetBytes(attributeRecord.Name); LfsRecord openAttributeRecord = WriteLogRecord(openAttributeOffset, 0, 0, 0, new List <long>(), NTFSLogOperation.OpenNonResidentAttribute, openAttributeBytes, NTFSLogOperation.Noop, attributeNameBytes, transactionID, false); } else { openAttributeOffset = OpenAttributeIndexToOffset(openAttributeIndex); if (!m_openAttributes[openAttributeIndex].AssociatedTransactions.Contains(transactionID)) { m_openAttributes[openAttributeIndex].AssociatedTransactions.Add(transactionID); } } } List <long> lcnList = new List <long>(); if (attributeRecord is NonResidentAttributeRecord) { long startVCN = (long)(streamOffset / (uint)Volume.BytesPerCluster); int clusterCount = (int)Math.Ceiling((double)structureLength / Volume.BytesPerCluster); for (long vcn = startVCN; vcn < startVCN + clusterCount; vcn++) { long lcn = ((NonResidentAttributeRecord)attributeRecord).DataRunSequence.GetDataClusterLCN(vcn); lcnList.Add(lcn); } } return(WriteLogRecord(openAttributeOffset, streamOffset, recordOffset, attributeOffset, lcnList, redoOperation, redoData, undoOperation, undoData, transactionID, flushToDisk)); }
public NTFSLogRecord ReadLogRecord(ulong lsn) { LfsRecord record = m_logFile.ReadRecord(lsn); if (record.RecordType == LfsRecordType.ClientRecord) { return(new NTFSLogRecord(record.Data)); } else { return(null); } }
public NTFSRestartRecord ReadRestartRecord(ulong clientRestartLsn) { LfsRecord record = m_logFile.ReadRecord(clientRestartLsn); if (record.RecordType == LfsRecordType.ClientRestart) { return(new NTFSRestartRecord(record.Data)); } else { string message = String.Format("Log restart area points to a record with RecordType {0}", record.RecordType); throw new InvalidDataException(message); } }
public List <LfsRecord> FindNextRecords(ulong lsn, int clientIndex) { LfsRecord record = ReadRecord(lsn); List <LfsRecord> result = new List <LfsRecord>(); do { record = FindNextRecord(record, clientIndex); if (record != null) { result.Add(record); } }while (record != null); return(result); }
public LfsRecord WriteRecord(int clientIndex, LfsRecordType recordType, ulong clientPreviousLsn, ulong clientUndoNextLsn, uint transactionId, byte[] clientData, bool flushToDisk) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } // It's perfectly valid to clear the CleanDismount flag after writing the transfer. // Note that CurrentLsn is used to determine which is restart page recent so we should not update the restart page without incrementing CurrentLsn first. ushort clientSeqNumber = m_restartPage.RestartArea.LogClientArray[clientIndex].SeqNumber; LfsRecord record = new LfsRecord(); record.ClientSeqNumber = clientSeqNumber; record.ClientIndex = (ushort)clientIndex; record.RecordType = recordType; record.ClientPreviousLsn = clientPreviousLsn; record.ClientUndoNextLsn = clientUndoNextLsn; record.TransactionId = transactionId; record.ClientDataLength = (uint)clientData.Length; record.Data = clientData; record.ThisLsn = GetNextLsn(); m_recordsPendingWrite.Add(record); bool endOfTransferRecorded = false; if (flushToDisk) { FlushRecords(m_recordsPendingWrite, out endOfTransferRecorded); m_recordsPendingWrite.Clear(); } // Update CurrentLsn / LastLsnDataLength m_restartPage.RestartArea.CurrentLsn = record.ThisLsn; m_restartPage.RestartArea.LastLsnDataLength = (uint)record.Data.Length; // If the client is writing a ClientRestart, the call to write a restart page (when updating the client record) is imminent, so no need to write a restart page now. // Note that CurrentLsn is used to determine which restart page is more recent so we should not update the restart page without incrementing CurrentLsn. if (recordType != LfsRecordType.ClientRestart && endOfTransferRecorded) { // 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 next page we write as the only transfer after the flushed LSN. WriteRestartPage(false); } return(record); }
private LfsRecord WriteLogRecord(NTFSLogRecord ntfsLogRecord, uint transactionID, bool flushToDisk) { LfsClientRecord clientRecord = m_logFile.GetClientRecord(m_clientIndex); byte[] clientData = ntfsLogRecord.GetBytes(); int transactionIndex = IndexOfTransaction(transactionID); ulong lastLsnToUndo = m_transactions[transactionIndex].LastLsnToUndo; LfsRecord result = m_logFile.WriteRecord(m_clientIndex, LfsRecordType.ClientRecord, m_lastClientLsn, lastLsnToUndo, transactionID, clientData, flushToDisk); m_lastClientLsn = result.ThisLsn; m_transactions[transactionIndex].LastLsnToUndo = result.ThisLsn; if (m_transactions[transactionIndex].OldestLsn == 0) { m_transactions[transactionIndex].OldestLsn = result.ThisLsn; } return(result); }
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); }
public LfsRecord WriteForgetTransactionRecord(uint transactionID, bool flushToDisk) { NTFSLogRecord ntfsLogRecord = new NTFSLogRecord(); ntfsLogRecord.RedoOperation = NTFSLogOperation.ForgetTransaction; ntfsLogRecord.UndoOperation = NTFSLogOperation.CompensationLogRecord; LfsRecord result = WriteLogRecord(ntfsLogRecord, transactionID, flushToDisk); DeallocateTransactionID(transactionID); // Update the open attribute table and remove any open attribute that no longer has an associated transaction for (int index = 0; index < m_openAttributes.Count; index++) { OpenAttribute openAttribute = m_openAttributes[index]; openAttribute.AssociatedTransactions.Remove(transactionID); if (openAttribute.AssociatedTransactions.Count == 0) { m_openAttributes.RemoveAt(index); index--; } } return(result); }
public List <NTFSLogRecord> FindRecordsToRedo() { NTFSRestartRecord restartRecord = ReadCurrentRestartRecord(); List <DirtyPageEntry> dirtyPageTable = ReadDirtyPageTable(restartRecord); ulong redoLsn = FindRedoLsn(dirtyPageTable); LfsRecord firstRecord = m_logFile.ReadRecord(redoLsn); List <LfsRecord> records = m_logFile.FindNextRecords(redoLsn, m_clientIndex); records.Insert(0, firstRecord); List <NTFSLogRecord> result = new List <NTFSLogRecord>(); foreach (LfsRecord record in records) { if (record.RecordType == LfsRecordType.ClientRecord) { NTFSLogRecord clientRecord = new NTFSLogRecord(record.Data); switch (clientRecord.RedoOperation) { case NTFSLogOperation.Noop: case NTFSLogOperation.OpenAttributeTableDump: case NTFSLogOperation.AttributeNamesDump: case NTFSLogOperation.DirtyPageTableDump: case NTFSLogOperation.TransactionTableDump: { continue; } default: { result.Add(clientRecord); break; } } } } return(result); }
public LfsRecord WriteRecord(int clientIndex, LfsRecordType recordType, ulong clientPreviousLsn, ulong clientUndoNextLsn, uint transactionId, byte[] clientData) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } if (m_restartPage.LogRestartArea.IsClean) { // We should clear the CleanDismount flag before making changes to the log file WriteRestartPage(false); } ushort clientSeqNumber = m_restartPage.LogRestartArea.LogClientArray[clientIndex].SeqNumber; LfsRecord record = new LfsRecord(); record.ClientSeqNumber = clientSeqNumber; record.ClientIndex = (ushort)clientIndex; record.RecordType = recordType; record.ClientPreviousLsn = clientPreviousLsn; record.ClientUndoNextLsn = clientUndoNextLsn; record.TransactionId = transactionId; record.ClientDataLength = (uint)clientData.Length; record.Data = clientData; record.ThisLsn = GetNextLsn(); WriteRecord(record); // Update CurrentLsn / LastLsnDataLength m_restartPage.LogRestartArea.CurrentLsn = record.ThisLsn; m_restartPage.LogRestartArea.LastLsnDataLength = (uint)record.Data.Length; if (record.IsMultiPageRecord) { // We can optimize by only writing the restart page if we already had one transfer after the last flushed LSN. WriteRestartPage(false); } return(record); }
public LfsRecord FindNextRecord(LfsRecord record) { return(m_logFile.FindNextRecord(record, m_clientIndex)); }
/// <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; } }
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++; } }
/// <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); } } } }