private LfsRestartPage ReadRestartPage() { byte[] firstPageBytes = ReadData(0, Volume.BytesPerSector); uint systemPageSize = LfsRestartPage.GetSystemPageSize(firstPageBytes, 0); int bytesToRead = (int)systemPageSize - firstPageBytes.Length; if (bytesToRead > 0) { byte[] temp = ReadData((ulong)firstPageBytes.Length, bytesToRead); firstPageBytes = ByteUtils.Concatenate(firstPageBytes, temp); } MultiSectorHelper.RevertUsaProtection(firstPageBytes, 0); LfsRestartPage firstRestartPage = new LfsRestartPage(firstPageBytes, 0); byte[] secondPageBytes = ReadData(systemPageSize, (int)systemPageSize); MultiSectorHelper.RevertUsaProtection(secondPageBytes, 0); LfsRestartPage secondRestartPage = new LfsRestartPage(secondPageBytes, 0); if (secondRestartPage.RestartArea.CurrentLsn > firstRestartPage.RestartArea.CurrentLsn) { m_restartPage = secondRestartPage; m_isFirstRestartPageTurn = true; } else { m_restartPage = firstRestartPage; } return(m_restartPage); }
public bool IsLogClean() { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } if (!m_restartPage.LogRestartArea.IsInUse) { // If the log file is not in use than it must be clean. return(true); } else if (m_restartPage.LogRestartArea.IsClean) { // If the clean bit is set than the log file must be clean. return(true); } else { // The volume has not been shutdown cleanly. // It's possible that the log is clean if the volume was completely idle for at least five seconds preceding the unclean shutdown. // Currently, we skip the analysis and assume that's not the case. return(false); } }
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 WriteRestartPage(LfsRestartPage restartPage) { byte[] pageBytes = restartPage.GetBytes((int)restartPage.SystemPageSize, true); // The NTFS v5.1 driver will always read both restart pages and compare the CurrentLsn to determine which is more recent (even if the CleanDismount flag is set) ulong offset = m_isFirstRestartPageTurn ? 0 : restartPage.SystemPageSize; WriteData(offset, pageBytes); m_isFirstRestartPageTurn = !m_isFirstRestartPageTurn; }
public LfsClientRecord GetClientRecord(int clientIndex) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } return(m_restartPage.LogRestartArea.LogClientArray[clientIndex]); }
private int LsnToRecordOffsetInPage(ulong lsn) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } return((int)((lsn << 3) & (m_restartPage.LogPageSize - 1))); }
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); }
/// <remarks> /// This method should only be called after properly setting the values in a client restart record /// </remarks> public void WriteRestartPage(bool isClean) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } m_restartPage.LogRestartArea.IsClean = isClean; m_restartPage.LogRestartArea.RevisionNumber++; WriteRestartPage(m_restartPage); }
private ulong GetNextLsn() { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } ulong currentLsn = m_restartPage.LogRestartArea.CurrentLsn; int currentLsnRecordLength = (int)(m_restartPage.LogRestartArea.RecordHeaderLength + m_restartPage.LogRestartArea.LastLsnDataLength); return(CalculateNextLsn(currentLsn, currentLsnRecordLength)); }
private LfsRestartPage ReadRestartPage() { byte[] pageBytes = ReadData(0, Volume.BytesPerSector); uint systemPageSize = LfsRestartPage.GetSystemPageSize(pageBytes, 0); int bytesToRead = (int)systemPageSize - pageBytes.Length; if (bytesToRead > 0) { byte[] temp = ReadData((ulong)pageBytes.Length, bytesToRead); pageBytes = ByteUtils.Concatenate(pageBytes, temp); } MultiSectorHelper.RevertUsaProtection(pageBytes, 0); m_restartPage = new LfsRestartPage(pageBytes, 0); return(m_restartPage); }
public int FindClientIndex(string clientName) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } for (int index = 0; index < m_restartPage.LogRestartArea.LogClientArray.Count; index++) { if (String.Equals(m_restartPage.LogRestartArea.LogClientArray[index].ClientName, clientName, StringComparison.OrdinalIgnoreCase)) { return(index); } } return(-1); }
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 LfsRecordPage ReadPageFromFile(ulong pageOffset) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } byte[] pageBytes = ReadData(pageOffset, (int)m_restartPage.LogPageSize); uint pageSignature = LittleEndianConverter.ToUInt32(pageBytes, 0); if (pageSignature == LfsRecordPage.UninitializedPageSignature) { return(null); } MultiSectorHelper.RevertUsaProtection(pageBytes, 0); return(new LfsRecordPage(pageBytes, m_restartPage.LogRestartArea.LogPageDataOffset)); }
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); }
private static void WriteLogFile(Volume volume, long logFileStartSector, long logFileSize, int bytesPerLogPage) { const int BytesPerSystemPage = 4096; LfsClientRecord ntfsClientRecord = new LfsClientRecord(NTFSLogClient.ClientName); LfsRestartPage restartPage = LfsRestartPage.Create(logFileSize, BytesPerSystemPage, bytesPerLogPage, ntfsClientRecord); long secondRestartPageStartSector = logFileStartSector + BytesPerSystemPage / volume.BytesPerSector; long firstRecordPageStartSector = logFileStartSector + 2 * BytesPerSystemPage / volume.BytesPerSector; byte[] restartPageBytes = restartPage.GetBytes(BytesPerSystemPage, true); volume.WriteSectors(logFileStartSector, restartPageBytes); volume.WriteSectors(secondRestartPageStartSector, restartPageBytes); byte[] recordPagesBytes = new byte[logFileSize - 2 * BytesPerSystemPage]; for (int index = 0; index < recordPagesBytes.Length; index += 4) { LittleEndianWriter.WriteUInt32(recordPagesBytes, index, LfsRecordPage.UninitializedPageSignature); } volume.WriteSectors(firstRecordPageStartSector, recordPagesBytes); }
private ulong GetNextLsn() { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } ulong currentLsn = m_restartPage.RestartArea.CurrentLsn; if (currentLsn == 0) // Freshly formatted disk { int bytesToSkip = (int)m_restartPage.SystemPageSize * 2 + (int)m_restartPage.LogPageSize * 2 + m_restartPage.RestartArea.LogPageDataOffset; return((uint)bytesToSkip >> 3); } else { int currentLsnRecordLength = (int)(m_restartPage.RestartArea.RecordHeaderLength + m_restartPage.RestartArea.LastLsnDataLength); return(CalculateNextLsn(currentLsn, currentLsnRecordLength)); } }
public bool IsClientInUse(int clientIndex) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } int nextClientInUseIndex = m_restartPage.RestartArea.ClientInUseList; while (nextClientInUseIndex != LfsRestartArea.NoClient) { if (nextClientInUseIndex == clientIndex) { return(true); } nextClientInUseIndex = m_restartPage.RestartArea.LogClientArray[nextClientInUseIndex].NextClient; } return(false); }
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); }
/// <remarks> /// This method should only be called after properly setting the values in a client restart record /// </remarks> public void WriteRestartPage(bool isClean) { if (m_restartPage == null) { m_restartPage = ReadRestartPage(); } if (m_recordsPendingWrite.Count > 0) { bool endOfTransferRecorded; FlushRecords(m_recordsPendingWrite, out endOfTransferRecorded); m_recordsPendingWrite.Clear(); } if (isClean && m_isTailCopyDirty) { // Copy the tail page to the correct location in the log file WritePage(m_tailPageOffsetInFile, m_tailPage); } m_restartPage.RestartArea.IsClean = isClean; m_restartPage.RestartArea.RevisionNumber++; WriteRestartPage(m_restartPage); }
private void WriteRestartPage(LfsRestartPage restartPage) { byte[] pageBytes = restartPage.GetBytes((int)restartPage.SystemPageSize, true); WriteData(0, pageBytes); WriteData(restartPage.SystemPageSize, pageBytes); }
/// <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); } } } }