public File(INtfsContext context, FileRecord baseRecord) { _context = context; _mft = _context.Mft; _records = new List<FileRecord>(); _records.Add(baseRecord); _indexCache = new ObjectCache<string, Index>(); _attributes = new List<NtfsAttribute>(); LoadAttributes(); }
private void MoveAttribute(FileRecord record, AttributeRecord attrRec, FileRecord targetRecord) { AttributeReference oldRef = new AttributeReference(record.Reference, attrRec.AttributeId); record.RemoveAttribute(attrRec.AttributeId); targetRecord.AddAttribute(attrRec); AttributeReference newRef = new AttributeReference(targetRecord.Reference, attrRec.AttributeId); foreach (var attr in _attributes) { attr.ReplaceExtent(oldRef, newRef, attrRec); } UpdateAttributeList(); }
private bool ExpelAttribute(FileRecord record) { if (record.MasterFileTableIndex == MasterFileTable.MftIndex) { // Special case for MFT - can't fully expel attributes, instead split most of the data runs off. List<AttributeRecord> attrs = record.Attributes; for (int i = attrs.Count - 1; i >= 0; --i) { AttributeRecord attr = attrs[i]; if (attr.AttributeType == AttributeType.Data) { if (SplitAttribute(record, (NonResidentAttributeRecord)attr, true)) { return true; } } } } else { List<AttributeRecord> attrs = record.Attributes; for (int i = attrs.Count - 1; i >= 0; --i) { AttributeRecord attr = attrs[i]; if (attr.AttributeType > AttributeType.AttributeList) { foreach (var targetRecord in _records) { if (_mft.RecordSize - targetRecord.Size >= attr.Size) { MoveAttribute(record, attr, targetRecord); return true; } } FileRecord newFileRecord = _mft.AllocateRecord(FileRecordFlags.None, record.IsMftRecord); newFileRecord.BaseFile = record.Reference; _records.Add(newFileRecord); MoveAttribute(record, attr, newFileRecord); return true; } } } return false; }
private bool SplitAttribute(FileRecord record, NonResidentAttributeRecord targetAttr, bool atStart) { if (targetAttr.DataRuns.Count <= 1) { return false; } int splitIndex = 1; if (!atStart) { List<DataRun> runs = targetAttr.DataRuns; splitIndex = runs.Count - 1; int saved = runs[splitIndex].Size; while (splitIndex > 1 && record.Size - saved > record.AllocatedSize) { --splitIndex; saved += runs[splitIndex].Size; } } AttributeRecord newAttr = targetAttr.Split(splitIndex); // Find a home for the new attribute record FileRecord newAttrHome = null; foreach (var targetRecord in _records) { if (!targetRecord.IsMftRecord && _mft.RecordSize - targetRecord.Size >= newAttr.Size) { targetRecord.AddAttribute(newAttr); newAttrHome = targetRecord; } } if (newAttrHome == null) { newAttrHome = _mft.AllocateRecord(_records[0].Flags & (~FileRecordFlags.InUse), record.IsMftRecord); newAttrHome.BaseFile = record.BaseFile.IsNull ? record.Reference : record.BaseFile; _records.Add(newAttrHome); newAttrHome.AddAttribute(newAttr); } // Add the new attribute record as an extent on the attribute it split from bool added = false; foreach (var attr in _attributes) { foreach (var existingRecord in attr.Extents) { if (existingRecord.Key.File == record.Reference && existingRecord.Key.AttributeId == targetAttr.AttributeId) { attr.AddExtent(newAttrHome.Reference, newAttr); added = true; break; } } if (added) { break; } } UpdateAttributeList(); return true; }
private bool SplitAttribute(FileRecord record) { if (record.Attributes.Count != 1) { throw new InvalidOperationException("Attempting to split attribute in MFT record containing multiple attributes"); } return SplitAttribute(record, (NonResidentAttributeRecord)record.FirstAttribute, false); }
public void WriteRecord(FileRecord record) { int recordSize = record.Size; if (recordSize > _recordLength) { throw new IOException("Attempting to write over-sized MFT record"); } byte[] buffer = new byte[_recordLength]; record.ToBytes(buffer, 0); _recordStream.Position = record.MasterFileTableIndex * (long)_recordLength; _recordStream.Write(buffer, 0, _recordLength); _recordStream.Flush(); // We may have modified our own meta-data by extending the data stream, so // make sure our records are up-to-date. if (_self.MftRecordIsDirty) { DirectoryEntry dirEntry = _self.DirectoryEntry; if (dirEntry != null) { dirEntry.UpdateFrom(_self); } _self.UpdateRecordInMft(); } // Need to update Mirror. OpenRaw is OK because this is short duration, and we don't // extend or otherwise modify any meta-data, just the content of the Data stream. if (record.MasterFileTableIndex < 4 && _self.Context.GetFileByIndex != null) { File mftMirror = _self.Context.GetFileByIndex(MftMirrorIndex); if (mftMirror != null) { using (Stream s = mftMirror.OpenStream(AttributeType.Data, null, FileAccess.ReadWrite)) { s.Position = record.MasterFileTableIndex * (long)_recordLength; s.Write(buffer, 0, _recordLength); } } } }
private bool VerifyMftRecord(byte[] recordData, bool presentInBitmap, int bytesPerSector) { bool ok = true; // // Verify the attributes seem OK... // byte[] tempBuffer = new byte[recordData.Length]; Array.Copy(recordData, tempBuffer, tempBuffer.Length); GenericFixupRecord genericRecord = new GenericFixupRecord(bytesPerSector); genericRecord.FromBytes(tempBuffer, 0); int pos = Utilities.ToUInt16LittleEndian(genericRecord.Content, 0x14); while (Utilities.ToUInt32LittleEndian(genericRecord.Content, pos) != 0xFFFFFFFF) { int attrLen; try { AttributeRecord ar = AttributeRecord.FromBytes(genericRecord.Content, pos, out attrLen); if (attrLen != ar.Size) { ReportError("Attribute size is different to calculated size. AttrId={0}", ar.AttributeId); ok = false; } if (ar.IsNonResident) { NonResidentAttributeRecord nrr = (NonResidentAttributeRecord)ar; if (nrr.DataRuns.Count > 0) { long totalVcn = 0; foreach (var run in nrr.DataRuns) { totalVcn += run.RunLength; } if (totalVcn != nrr.LastVcn - nrr.StartVcn + 1) { ReportError("Declared VCNs doesn't match data runs. AttrId={0}", ar.AttributeId); ok = false; } } } } catch { ReportError("Failure parsing attribute at pos={0}", pos); return(false); } pos += attrLen; } // // Now consider record as a whole // FileRecord record = new FileRecord(bytesPerSector); record.FromBytes(recordData, 0); bool inUse = (record.Flags & FileRecordFlags.InUse) != 0; if (inUse != presentInBitmap) { ReportError("MFT bitmap and record in-use flag don't agree. Mft={0}, Record={1}", presentInBitmap ? "InUse" : "Free", inUse ? "InUse" : "Free"); ok = false; } if (record.Size != record.RealSize) { ReportError("MFT record real size is different to calculated size. Stored in MFT={0}, Calculated={1}", record.RealSize, record.Size); ok = false; } if (Utilities.ToUInt32LittleEndian(recordData, (int)record.RealSize - 8) != uint.MaxValue) { ReportError("MFT record is not correctly terminated with 0xFFFFFFFF"); ok = false; } return(ok); }
public FileRecord AllocateRecord(long index, FileRecordFlags flags) { _bitmap.MarkPresent(index); FileRecord newRecord = new FileRecord(_bytesPerSector, _recordLength, (uint)index); _recordCache[index] = newRecord; newRecord.Flags = FileRecordFlags.InUse | flags; WriteRecord(newRecord); _self.UpdateRecordInMft(); return newRecord; }
public FileRecord GetRecord(long index, bool ignoreMagic, bool ignoreBitmap) { if (ignoreBitmap || _bitmap == null || _bitmap.IsPresent(index)) { FileRecord result = _recordCache[index]; if (result != null) { return result; } if ((index + 1) * _recordLength <= _recordStream.Length) { _recordStream.Position = index * _recordLength; byte[] recordBuffer = Utilities.ReadFully(_recordStream, _recordLength); result = new FileRecord(_bytesPerSector); result.FromBytes(recordBuffer, 0, ignoreMagic); result.LoadedIndex = (uint)index; } else { result = new FileRecord(_bytesPerSector, _recordLength, (uint)index); } _recordCache[index] = result; return result; } return null; }
public File InitializeNew(INtfsContext context, long firstBitmapCluster, ulong numBitmapClusters, long firstRecordsCluster, ulong numRecordsClusters) { BiosParameterBlock bpb = context.BiosParameterBlock; FileRecord fileRec = new FileRecord(bpb.BytesPerSector, bpb.MftRecordSize, (uint)MftIndex); fileRec.Flags = FileRecordFlags.InUse; fileRec.SequenceNumber = 1; _recordCache[MftIndex] = fileRec; _self = new File(context, fileRec); StandardInformation.InitializeNewFile(_self, FileAttributeFlags.Hidden | FileAttributeFlags.System); NtfsStream recordsStream = _self.CreateStream(AttributeType.Data, null, firstRecordsCluster, numRecordsClusters, (uint)bpb.BytesPerCluster); _recordStream = recordsStream.Open(FileAccess.ReadWrite); Wipe(_recordStream); NtfsStream bitmapStream = _self.CreateStream(AttributeType.Bitmap, null, firstBitmapCluster, numBitmapClusters, (uint)bpb.BytesPerCluster); using (Stream s = bitmapStream.Open(FileAccess.ReadWrite)) { Wipe(s); s.SetLength(8); _bitmap = new Bitmap(s, long.MaxValue); } _recordLength = context.BiosParameterBlock.MftRecordSize; _bytesPerSector = context.BiosParameterBlock.BytesPerSector; _bitmap.MarkPresentRange(0, 1); // Write the MFT's own record to itself byte[] buffer = new byte[_recordLength]; fileRec.ToBytes(buffer, 0); _recordStream.Position = 0; _recordStream.Write(buffer, 0, _recordLength); _recordStream.Flush(); return _self; }
public FileRecord AllocateRecord(FileRecordFlags flags, bool isMft) { long index; if (isMft) { // Have to take a lot of care extending the MFT itself, to ensure we never end up unable to // bootstrap the file system via the MFT itself - hence why special records are reserved // for MFT's own MFT record overflow. for (int i = 15; i > 11; --i) { FileRecord r = GetRecord(i, false); if (r.BaseFile.SequenceNumber == 0) { r.Reset(); r.Flags |= FileRecordFlags.InUse; WriteRecord(r); return r; } } throw new IOException("MFT too fragmented - unable to allocate MFT overflow record"); } else { index = _bitmap.AllocateFirstAvailable(FirstAvailableMftIndex); } if (index * _recordLength >= _recordStream.Length) { // Note: 64 is significant, since bitmap extends by 8 bytes (=64 bits) at a time. long newEndIndex = Utilities.RoundUp(index + 1, 64); _recordStream.SetLength(newEndIndex * _recordLength); for (long i = index; i < newEndIndex; ++i) { FileRecord record = new FileRecord(_bytesPerSector, _recordLength, (uint)i); WriteRecord(record); } } FileRecord newRecord = GetRecord(index, true); newRecord.ReInitialize(_bytesPerSector, _recordLength, (uint)index); _recordCache[index] = newRecord; newRecord.Flags = FileRecordFlags.InUse | flags; WriteRecord(newRecord); _self.UpdateRecordInMft(); return newRecord; }
public FileRecord GetBootstrapRecord() { _recordStream.Position = 0; byte[] mftSelfRecordData = Utilities.ReadFully(_recordStream, _recordLength); FileRecord mftSelfRecord = new FileRecord(_bytesPerSector); mftSelfRecord.FromBytes(mftSelfRecordData, 0); _recordCache[MftIndex] = mftSelfRecord; return mftSelfRecord; }
private bool VerifyMftRecord(byte[] recordData, bool presentInBitmap, int bytesPerSector) { bool ok = true; // // Verify the attributes seem OK... // byte[] tempBuffer = new byte[recordData.Length]; Array.Copy(recordData, tempBuffer, tempBuffer.Length); GenericFixupRecord genericRecord = new GenericFixupRecord(bytesPerSector); genericRecord.FromBytes(tempBuffer, 0); int pos = Utilities.ToUInt16LittleEndian(genericRecord.Content, 0x14); while (Utilities.ToUInt32LittleEndian(genericRecord.Content, pos) != 0xFFFFFFFF) { int attrLen; try { AttributeRecord ar = AttributeRecord.FromBytes(genericRecord.Content, pos, out attrLen); if (attrLen != ar.Size) { ReportError("Attribute size is different to calculated size. AttrId={0}", ar.AttributeId); ok = false; } if (ar.IsNonResident) { NonResidentAttributeRecord nrr = (NonResidentAttributeRecord)ar; if (nrr.DataRuns.Count > 0) { long totalVcn = 0; foreach (var run in nrr.DataRuns) { totalVcn += run.RunLength; } if (totalVcn != nrr.LastVcn - nrr.StartVcn + 1) { ReportError("Declared VCNs doesn't match data runs. AttrId={0}", ar.AttributeId); ok = false; } } } } catch { ReportError("Failure parsing attribute at pos={0}", pos); return false; } pos += attrLen; } // // Now consider record as a whole // FileRecord record = new FileRecord(bytesPerSector); record.FromBytes(recordData, 0); bool inUse = (record.Flags & FileRecordFlags.InUse) != 0; if (inUse != presentInBitmap) { ReportError("MFT bitmap and record in-use flag don't agree. Mft={0}, Record={1}", presentInBitmap ? "InUse" : "Free", inUse ? "InUse" : "Free"); ok = false; } if (record.Size != record.RealSize) { ReportError("MFT record real size is different to calculated size. Stored in MFT={0}, Calculated={1}", record.RealSize, record.Size); ok = false; } if (Utilities.ToUInt32LittleEndian(recordData, (int)record.RealSize - 8) != uint.MaxValue) { ReportError("MFT record is not correctly terminated with 0xFFFFFFFF"); ok = false; } return ok; }
public Directory(INtfsContext context, FileRecord baseRecord) : base(context, baseRecord) { }
private bool SplitAttribute(FileRecord record, NonResidentAttributeRecord targetAttr, bool atStart) { if (targetAttr.DataRuns.Count <= 1) { return(false); } int splitIndex = 1; if (!atStart) { List <DataRun> runs = targetAttr.DataRuns; splitIndex = runs.Count - 1; int saved = runs[splitIndex].Size; while (splitIndex > 1 && record.Size - saved > record.AllocatedSize) { --splitIndex; saved += runs[splitIndex].Size; } } AttributeRecord newAttr = targetAttr.Split(splitIndex); // Find a home for the new attribute record FileRecord newAttrHome = null; foreach (var targetRecord in _records) { if (!targetRecord.IsMftRecord && _mft.RecordSize - targetRecord.Size >= newAttr.Size) { targetRecord.AddAttribute(newAttr); newAttrHome = targetRecord; } } if (newAttrHome == null) { newAttrHome = _mft.AllocateRecord(_records[0].Flags & (~FileRecordFlags.InUse), record.IsMftRecord); newAttrHome.BaseFile = record.BaseFile.IsNull ? record.Reference : record.BaseFile; _records.Add(newAttrHome); newAttrHome.AddAttribute(newAttr); } // Add the new attribute record as an extent on the attribute it split from bool added = false; foreach (var attr in _attributes) { foreach (var existingRecord in attr.Extents) { if (existingRecord.Key.File == record.Reference && existingRecord.Key.AttributeId == targetAttr.AttributeId) { attr.AddExtent(newAttrHome.Reference, newAttr); added = true; break; } } if (added) { break; } } UpdateAttributeList(); return(true); }
private void LoadAttributes() { Dictionary <long, FileRecord> extraFileRecords = new Dictionary <long, FileRecord>(); AttributeRecord attrListRec = _records[0].GetAttribute(AttributeType.AttributeList); if (attrListRec != null) { NtfsAttribute lastAttr = null; StructuredNtfsAttribute <AttributeList> attrListAttr = (StructuredNtfsAttribute <AttributeList>)NtfsAttribute.FromRecord(this, MftReference, attrListRec); var attrList = attrListAttr.Content; _attributes.Add(attrListAttr); foreach (var record in attrList) { FileRecord attrFileRecord = _records[0]; if (record.BaseFileReference.MftIndex != _records[0].MasterFileTableIndex) { if (!extraFileRecords.TryGetValue(record.BaseFileReference.MftIndex, out attrFileRecord)) { attrFileRecord = _context.Mft.GetRecord(record.BaseFileReference); if (attrFileRecord != null) { extraFileRecords[attrFileRecord.MasterFileTableIndex] = attrFileRecord; } } } if (attrFileRecord != null) { AttributeRecord attrRec = attrFileRecord.GetAttribute(record.AttributeId); if (attrRec != null) { if (record.StartVcn == 0) { lastAttr = NtfsAttribute.FromRecord(this, record.BaseFileReference, attrRec); _attributes.Add(lastAttr); } else { lastAttr.AddExtent(record.BaseFileReference, attrRec); } } } } foreach (var extraFileRecord in extraFileRecords) { _records.Add(extraFileRecord.Value); } } else { foreach (var record in _records[0].Attributes) { _attributes.Add(NtfsAttribute.FromRecord(this, MftReference, record)); } } }