mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-06-14 03:16:40 +10:00
523 lines
19 KiB
C#
523 lines
19 KiB
C#
//
|
|
// 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;
|
|
|
|
/// <summary>
|
|
/// Class representing the $MFT file on disk, including mirror.
|
|
/// </summary>
|
|
/// <remarks>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.</remarks>
|
|
internal class MasterFileTable : IDiagnosticTraceable, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// MFT index of the MFT file itself.
|
|
/// </summary>
|
|
public const long MftIndex = 0;
|
|
|
|
/// <summary>
|
|
/// MFT index of the MFT Mirror file.
|
|
/// </summary>
|
|
public const long MftMirrorIndex = 1;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Log file.
|
|
/// </summary>
|
|
public const long LogFileIndex = 2;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Volume file.
|
|
/// </summary>
|
|
public const long VolumeIndex = 3;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Attribute Definition file.
|
|
/// </summary>
|
|
public const long AttrDefIndex = 4;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Root Directory.
|
|
/// </summary>
|
|
public const long RootDirIndex = 5;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Bitmap file.
|
|
/// </summary>
|
|
public const long BitmapIndex = 6;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Boot sector(s).
|
|
/// </summary>
|
|
public const long BootIndex = 7;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Bad Bluster file.
|
|
/// </summary>
|
|
public const long BadClusIndex = 8;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Security Descriptor file.
|
|
/// </summary>
|
|
public const long SecureIndex = 9;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Uppercase mapping file.
|
|
/// </summary>
|
|
public const long UpCaseIndex = 10;
|
|
|
|
/// <summary>
|
|
/// MFT Index of the Optional Extensions directory.
|
|
/// </summary>
|
|
public const long ExtendIndex = 11;
|
|
|
|
/// <summary>
|
|
/// First MFT Index available for 'normal' files.
|
|
/// </summary>
|
|
private const uint FirstAvailableMftIndex = 24;
|
|
|
|
private File _self;
|
|
private Bitmap _bitmap;
|
|
private Stream _recordStream;
|
|
private ObjectCache<long, FileRecord> _recordCache;
|
|
|
|
private int _recordLength;
|
|
private int _bytesPerSector;
|
|
|
|
public MasterFileTable(INtfsContext context)
|
|
{
|
|
BiosParameterBlock bpb = context.BiosParameterBlock;
|
|
|
|
_recordCache = new ObjectCache<long, FileRecord>();
|
|
_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; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the MFT records directly from the MFT stream - bypassing the record cache.
|
|
/// </summary>
|
|
public IEnumerable<FileRecord> 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<object, string[]> fileToPaths = new Dictionary<object, string[]>();
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|