// // Copyright (c) 2008-2011, Kenneth Bell // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // namespace DiscUtils.Ntfs { using System; using System.Collections.Generic; using System.Globalization; using System.IO; /// /// Class representing the $MFT file on disk, including mirror. /// /// This class only understands basic record structure, and is /// ignorant of files that span multiple records. This class should only /// be used by the NtfsFileSystem and File classes. internal class MasterFileTable : IDiagnosticTraceable, IDisposable { /// /// MFT index of the MFT file itself. /// public const long MftIndex = 0; /// /// MFT index of the MFT Mirror file. /// public const long MftMirrorIndex = 1; /// /// MFT Index of the Log file. /// public const long LogFileIndex = 2; /// /// MFT Index of the Volume file. /// public const long VolumeIndex = 3; /// /// MFT Index of the Attribute Definition file. /// public const long AttrDefIndex = 4; /// /// MFT Index of the Root Directory. /// public const long RootDirIndex = 5; /// /// MFT Index of the Bitmap file. /// public const long BitmapIndex = 6; /// /// MFT Index of the Boot sector(s). /// public const long BootIndex = 7; /// /// MFT Index of the Bad Bluster file. /// public const long BadClusIndex = 8; /// /// MFT Index of the Security Descriptor file. /// public const long SecureIndex = 9; /// /// MFT Index of the Uppercase mapping file. /// public const long UpCaseIndex = 10; /// /// MFT Index of the Optional Extensions directory. /// public const long ExtendIndex = 11; /// /// First MFT Index available for 'normal' files. /// private const uint FirstAvailableMftIndex = 24; private File _self; private Bitmap _bitmap; private Stream _recordStream; private ObjectCache _recordCache; private int _recordLength; private int _bytesPerSector; public MasterFileTable(INtfsContext context) { BiosParameterBlock bpb = context.BiosParameterBlock; _recordCache = new ObjectCache(); _recordLength = bpb.MftRecordSize; _bytesPerSector = bpb.BytesPerSector; // Temporary record stream - until we've bootstrapped the MFT properly _recordStream = new SubStream(context.RawStream, bpb.MftCluster * bpb.SectorsPerCluster * bpb.BytesPerSector, 24 * _recordLength); } public int RecordSize { get { return _recordLength; } } /// /// Gets the MFT records directly from the MFT stream - bypassing the record cache. /// public IEnumerable Records { get { using (Stream mftStream = _self.OpenStream(AttributeType.Data, null, FileAccess.Read)) { uint index = 0; while (mftStream.Position < mftStream.Length) { byte[] recordData = Utilities.ReadFully(mftStream, _recordLength); if (Utilities.BytesToString(recordData, 0, 4) != "FILE") { continue; } FileRecord record = new FileRecord(_bytesPerSector); record.FromBytes(recordData, 0); record.LoadedIndex = index; yield return record; index++; } } } } public void Dispose() { if (_recordStream != null) { _recordStream.Dispose(); _recordStream = null; } if (_bitmap != null) { _bitmap.Dispose(); _bitmap = null; } GC.SuppressFinalize(this); } 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; } public void Initialize(File file) { _self = file; if (_recordStream != null) { _recordStream.Dispose(); } NtfsStream bitmapStream = _self.GetStream(AttributeType.Bitmap, null); _bitmap = new Bitmap(bitmapStream.Open(FileAccess.ReadWrite), long.MaxValue); NtfsStream recordsStream = _self.GetStream(AttributeType.Data, null); _recordStream = recordsStream.Open(FileAccess.ReadWrite); } 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 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 void RemoveRecord(FileRecordReference fileRef) { FileRecord record = GetRecord(fileRef.MftIndex, false); record.Reset(); WriteRecord(record); _recordCache.Remove(fileRef.MftIndex); _bitmap.MarkAbsent(fileRef.MftIndex); _self.UpdateRecordInMft(); } public FileRecord GetRecord(FileRecordReference fileReference) { FileRecord result = GetRecord(fileReference.MftIndex, false); if (result != null) { if (fileReference.SequenceNumber != 0 && result.SequenceNumber != 0) { if (fileReference.SequenceNumber != result.SequenceNumber) { throw new IOException("Attempt to get an MFT record with an old reference"); } } } return result; } public FileRecord GetRecord(long index, bool ignoreMagic) { return GetRecord(index, ignoreMagic, false); } 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 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); } } } } public long GetRecordOffset(FileRecordReference fileReference) { return fileReference.MftIndex * _recordLength; } public ClusterMap GetClusterMap() { int totalClusters = (int)Utilities.Ceil(_self.Context.BiosParameterBlock.TotalSectors64, _self.Context.BiosParameterBlock.SectorsPerCluster); ClusterRoles[] clusterToRole = new ClusterRoles[totalClusters]; object[] clusterToFile = new object[totalClusters]; Dictionary fileToPaths = new Dictionary(); for (int i = 0; i < totalClusters; ++i) { clusterToRole[i] = ClusterRoles.Free; } foreach (FileRecord fr in Records) { if (fr.BaseFile.Value != 0 || (fr.Flags & FileRecordFlags.InUse) == 0) { continue; } File f = new File(_self.Context, fr); foreach (var stream in f.AllStreams) { string fileId; if (stream.AttributeType == AttributeType.Data && !string.IsNullOrEmpty(stream.Name)) { fileId = f.IndexInMft.ToString(CultureInfo.InvariantCulture) + ":" + stream.Name; fileToPaths[fileId] = Utilities.Map(f.Names, n => n + ":" + stream.Name); } else { fileId = f.IndexInMft.ToString(CultureInfo.InvariantCulture); fileToPaths[fileId] = f.Names.ToArray(); } ClusterRoles roles = ClusterRoles.None; if (f.IndexInMft < MasterFileTable.FirstAvailableMftIndex) { roles |= ClusterRoles.SystemFile; if (f.IndexInMft == MasterFileTable.BootIndex) { roles |= ClusterRoles.BootArea; } } else { roles |= ClusterRoles.DataFile; } if (stream.AttributeType != AttributeType.Data) { roles |= ClusterRoles.Metadata; } foreach (var range in stream.GetClusters()) { for (long cluster = range.Offset; cluster < range.Offset + range.Count; ++cluster) { clusterToRole[cluster] = roles; clusterToFile[cluster] = fileId; } } } } return new ClusterMap(clusterToRole, clusterToFile, fileToPaths); } public void Dump(TextWriter writer, string indent) { writer.WriteLine(indent + "MASTER FILE TABLE"); writer.WriteLine(indent + " Record Length: " + _recordLength); foreach (var record in Records) { record.Dump(writer, indent + " "); foreach (AttributeRecord attr in record.Attributes) { attr.Dump(writer, indent + " "); } } } private static void Wipe(Stream s) { s.Position = 0; byte[] buffer = new byte[64 * Sizes.OneKiB]; int numWiped = 0; while (numWiped < s.Length) { int toWrite = (int)Math.Min(buffer.Length, s.Length - s.Position); s.Write(buffer, 0, toWrite); numWiped += toWrite; } } } }