Initial commit - WPinternals 2.6

This commit is contained in:
Rene Lergner
2018-10-25 22:35:49 +02:00
commit 396ae57f05
483 changed files with 159677 additions and 0 deletions
@@ -0,0 +1,86 @@
//
// 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.Text;
internal enum AttributeCollationRule : int
{
Binary = 0x00000000,
Filename = 0x00000001,
UnicodeString = 0x00000002,
UnsignedLong = 0x00000010,
Sid = 0x00000011,
SecurityHash = 0x00000012,
MultipleUnsignedLongs = 0x00000013
}
[Flags]
internal enum AttributeTypeFlags : int
{
None = 0x00,
Indexed = 0x02,
Multiple = 0x04,
NotZero = 0x08,
IndexedUnique = 0x10,
NamedUnique = 0x20,
MustBeResident = 0x40,
CanBeNonResident = 0x80
}
internal sealed class AttributeDefinitionRecord
{
public const int Size = 0xA0;
public string Name;
public AttributeType Type;
public uint DisplayRule;
public AttributeCollationRule CollationRule;
public AttributeTypeFlags Flags;
public long MinSize;
public long MaxSize;
internal void Read(byte[] buffer, int offset)
{
Name = Encoding.Unicode.GetString(buffer, offset + 0, 128).Trim('\0');
Type = (AttributeType)Utilities.ToUInt32LittleEndian(buffer, offset + 0x80);
DisplayRule = Utilities.ToUInt32LittleEndian(buffer, offset + 0x84);
CollationRule = (AttributeCollationRule)Utilities.ToUInt32LittleEndian(buffer, offset + 0x88);
Flags = (AttributeTypeFlags)Utilities.ToUInt32LittleEndian(buffer, offset + 0x8C);
MinSize = Utilities.ToInt64LittleEndian(buffer, offset + 0x90);
MaxSize = Utilities.ToInt64LittleEndian(buffer, offset + 0x98);
}
internal void Write(byte[] buffer, int offset)
{
Encoding.Unicode.GetBytes(Name, 0, Name.Length, buffer, offset + 0);
Utilities.WriteBytesLittleEndian((uint)Type, buffer, offset + 0x80);
Utilities.WriteBytesLittleEndian(DisplayRule, buffer, offset + 0x84);
Utilities.WriteBytesLittleEndian((uint)CollationRule, buffer, offset + 0x88);
Utilities.WriteBytesLittleEndian((uint)Flags, buffer, offset + 0x8C);
Utilities.WriteBytesLittleEndian(MinSize, buffer, offset + 0x90);
Utilities.WriteBytesLittleEndian(MaxSize, buffer, offset + 0x98);
}
}
}
+143
View File
@@ -0,0 +1,143 @@
//
// 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.IO;
internal sealed class AttributeDefinitions
{
private Dictionary<AttributeType, AttributeDefinitionRecord> _attrDefs;
public AttributeDefinitions()
{
_attrDefs = new Dictionary<AttributeType, AttributeDefinitionRecord>();
Add(AttributeType.StandardInformation, "$STANDARD_INFORMATION", AttributeTypeFlags.MustBeResident, 0x30, 0x48);
Add(AttributeType.AttributeList, "$ATTRIBUTE_LIST", AttributeTypeFlags.CanBeNonResident, 0, -1);
Add(AttributeType.FileName, "$FILE_NAME", AttributeTypeFlags.Indexed | AttributeTypeFlags.MustBeResident, 0x44, 0x242);
Add(AttributeType.ObjectId, "$OBJECT_ID", AttributeTypeFlags.MustBeResident, 0, 0x100);
Add(AttributeType.SecurityDescriptor, "$SECURITY_DESCRIPTOR", AttributeTypeFlags.CanBeNonResident, 0x0, -1);
Add(AttributeType.VolumeName, "$VOLUME_NAME", AttributeTypeFlags.MustBeResident, 0x2, 0x100);
Add(AttributeType.VolumeInformation, "$VOLUME_INFORMATION", AttributeTypeFlags.MustBeResident, 0xC, 0xC);
Add(AttributeType.Data, "$DATA", AttributeTypeFlags.None, 0, -1);
Add(AttributeType.IndexRoot, "$INDEX_ROOT", AttributeTypeFlags.MustBeResident, 0, -1);
Add(AttributeType.IndexAllocation, "$INDEX_ALLOCATION", AttributeTypeFlags.CanBeNonResident, 0, -1);
Add(AttributeType.Bitmap, "$BITMAP", AttributeTypeFlags.CanBeNonResident, 0, -1);
Add(AttributeType.ReparsePoint, "$REPARSE_POINT", AttributeTypeFlags.CanBeNonResident, 0, 0x4000);
Add(AttributeType.ExtendedAttributesInformation, "$EA_INFORMATION", AttributeTypeFlags.MustBeResident, 0x8, 0x8);
Add(AttributeType.ExtendedAttributes, "$EA", AttributeTypeFlags.None, 0, 0x10000);
Add(AttributeType.LoggedUtilityStream, "$LOGGED_UTILITY_STREAM", AttributeTypeFlags.CanBeNonResident, 0, 0x10000);
}
public AttributeDefinitions(File file)
{
_attrDefs = new Dictionary<AttributeType, AttributeDefinitionRecord>();
byte[] buffer = new byte[AttributeDefinitionRecord.Size];
using (Stream s = file.OpenStream(AttributeType.Data, null, FileAccess.Read))
{
while (Utilities.ReadFully(s, buffer, 0, buffer.Length) == buffer.Length)
{
AttributeDefinitionRecord record = new AttributeDefinitionRecord();
record.Read(buffer, 0);
// NULL terminator record
if (record.Type != AttributeType.None)
{
_attrDefs.Add(record.Type, record);
}
}
}
}
public void WriteTo(File file)
{
List<AttributeType> attribs = new List<AttributeType>(_attrDefs.Keys);
attribs.Sort();
using (Stream s = file.OpenStream(AttributeType.Data, null, FileAccess.ReadWrite))
{
byte[] buffer;
for (int i = 0; i < attribs.Count; ++i)
{
buffer = new byte[AttributeDefinitionRecord.Size];
AttributeDefinitionRecord attrDef = _attrDefs[attribs[i]];
attrDef.Write(buffer, 0);
s.Write(buffer, 0, buffer.Length);
}
buffer = new byte[AttributeDefinitionRecord.Size];
s.Write(buffer, 0, buffer.Length);
}
}
internal AttributeDefinitionRecord Lookup(string name)
{
foreach (var record in _attrDefs.Values)
{
if (string.Compare(name, record.Name, StringComparison.OrdinalIgnoreCase) == 0)
{
return record;
}
}
return null;
}
internal bool MustBeResident(AttributeType attributeType)
{
AttributeDefinitionRecord record;
if (_attrDefs.TryGetValue(attributeType, out record))
{
return (record.Flags & AttributeTypeFlags.MustBeResident) != 0;
}
return false;
}
internal bool IsIndexed(AttributeType attributeType)
{
AttributeDefinitionRecord record;
if (_attrDefs.TryGetValue(attributeType, out record))
{
return (record.Flags & AttributeTypeFlags.Indexed) != 0;
}
return false;
}
private void Add(AttributeType attributeType, string name, AttributeTypeFlags attributeTypeFlags, int minSize, int maxSize)
{
AttributeDefinitionRecord adr = new AttributeDefinitionRecord();
adr.Type = attributeType;
adr.Name = name;
adr.Flags = attributeTypeFlags;
adr.MinSize = minSize;
adr.MaxSize = maxSize;
_attrDefs.Add(attributeType, adr);
}
}
}
+140
View File
@@ -0,0 +1,140 @@
//
// 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.Collections;
using System.Collections.Generic;
using System.IO;
internal class AttributeList : IByteArraySerializable, IDiagnosticTraceable, ICollection<AttributeListRecord>
{
private List<AttributeListRecord> _records;
public AttributeList()
{
_records = new List<AttributeListRecord>();
}
public int Size
{
get
{
int total = 0;
foreach (var record in _records)
{
total += record.Size;
}
return total;
}
}
public int Count
{
get { return _records.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public int ReadFrom(byte[] buffer, int offset)
{
_records.Clear();
int pos = 0;
while (pos < buffer.Length)
{
AttributeListRecord r = new AttributeListRecord();
pos += r.ReadFrom(buffer, offset + pos);
_records.Add(r);
}
return pos;
}
public void WriteTo(byte[] buffer, int offset)
{
int pos = offset;
foreach (var record in _records)
{
record.WriteTo(buffer, offset + pos);
pos += record.Size;
}
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "ATTRIBUTE LIST RECORDS");
foreach (AttributeListRecord r in _records)
{
r.Dump(writer, indent + " ");
}
}
public void Add(AttributeListRecord item)
{
_records.Add(item);
_records.Sort();
}
public void Clear()
{
_records.Clear();
}
public bool Contains(AttributeListRecord item)
{
return _records.Contains(item);
}
public void CopyTo(AttributeListRecord[] array, int arrayIndex)
{
_records.CopyTo(array, arrayIndex);
}
public bool Remove(AttributeListRecord item)
{
return _records.Remove(item);
}
#region IEnumerable<AttributeListRecord> Members
public IEnumerator<AttributeListRecord> GetEnumerator()
{
return _records.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return _records.GetEnumerator();
}
#endregion
}
}
+145
View File
@@ -0,0 +1,145 @@
//
// 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.IO;
using System.Text;
internal class AttributeListRecord : IDiagnosticTraceable, IByteArraySerializable, IComparable<AttributeListRecord>
{
public AttributeType Type;
public ushort RecordLength;
public byte NameLength;
public byte NameOffset;
public string Name;
public ulong StartVcn;
public FileRecordReference BaseFileReference;
public ushort AttributeId;
public int Size
{
get
{
return Utilities.RoundUp(0x20 + (string.IsNullOrEmpty(Name) ? 0 : Encoding.Unicode.GetByteCount(Name)), 8);
}
}
public static AttributeListRecord FromAttribute(AttributeRecord attr, FileRecordReference mftRecord)
{
AttributeListRecord newRecord = new AttributeListRecord()
{
Type = attr.AttributeType,
Name = attr.Name,
StartVcn = 0,
BaseFileReference = mftRecord,
AttributeId = attr.AttributeId
};
if (attr.IsNonResident)
{
newRecord.StartVcn = (ulong)((NonResidentAttributeRecord)attr).StartVcn;
}
return newRecord;
}
public int ReadFrom(byte[] data, int offset)
{
Type = (AttributeType)Utilities.ToUInt32LittleEndian(data, offset + 0x00);
RecordLength = Utilities.ToUInt16LittleEndian(data, offset + 0x04);
NameLength = data[offset + 0x06];
NameOffset = data[offset + 0x07];
StartVcn = Utilities.ToUInt64LittleEndian(data, offset + 0x08);
BaseFileReference = new FileRecordReference(Utilities.ToUInt64LittleEndian(data, offset + 0x10));
AttributeId = Utilities.ToUInt16LittleEndian(data, offset + 0x18);
if (NameLength > 0)
{
Name = Encoding.Unicode.GetString(data, offset + NameOffset, NameLength * 2);
}
else
{
Name = null;
}
if (RecordLength < 0x18)
{
throw new InvalidDataException("Malformed AttributeList record");
}
return RecordLength;
}
public void WriteTo(byte[] buffer, int offset)
{
NameOffset = 0x20;
if (string.IsNullOrEmpty(Name))
{
NameLength = 0;
}
else
{
NameLength = (byte)(Encoding.Unicode.GetBytes(Name, 0, Name.Length, buffer, offset + NameOffset) / 2);
}
RecordLength = (ushort)Utilities.RoundUp(NameOffset + (NameLength * 2), 8);
Utilities.WriteBytesLittleEndian((uint)Type, buffer, offset);
Utilities.WriteBytesLittleEndian(RecordLength, buffer, offset + 0x04);
buffer[offset + 0x06] = NameLength;
buffer[offset + 0x07] = NameOffset;
Utilities.WriteBytesLittleEndian(StartVcn, buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian(BaseFileReference.Value, buffer, offset + 0x10);
Utilities.WriteBytesLittleEndian(AttributeId, buffer, offset + 0x18);
}
public int CompareTo(AttributeListRecord other)
{
int val = ((int)Type) - (int)other.Type;
if (val != 0)
{
return val;
}
val = string.Compare(Name, other.Name, StringComparison.OrdinalIgnoreCase);
if (val != 0)
{
return val;
}
return ((int)StartVcn) - (int)other.StartVcn;
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "ATTRIBUTE LIST RECORD");
writer.WriteLine(indent + " Type: " + Type);
writer.WriteLine(indent + " Record Length: " + RecordLength);
writer.WriteLine(indent + " Name: " + Name);
writer.WriteLine(indent + " Start VCN: " + StartVcn);
writer.WriteLine(indent + " Base File Reference: " + BaseFileReference);
writer.WriteLine(indent + " Attribute ID: " + AttributeId);
}
}
}
+199
View File
@@ -0,0 +1,199 @@
//
// 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.IO;
using System.Text;
[Flags]
internal enum AttributeFlags : ushort
{
None = 0x0000,
Compressed = 0x0001,
Encrypted = 0x4000,
Sparse = 0x8000
}
internal abstract class AttributeRecord : IComparable<AttributeRecord>
{
protected AttributeType _type;
protected byte _nonResidentFlag;
protected AttributeFlags _flags;
protected ushort _attributeId;
protected string _name;
public AttributeRecord()
{
}
public AttributeRecord(AttributeType type, string name, ushort id, AttributeFlags flags)
{
_type = type;
_name = name;
_attributeId = id;
_flags = flags;
}
public AttributeType AttributeType
{
get { return _type; }
}
public ushort AttributeId
{
get { return _attributeId; }
set { _attributeId = value; }
}
public abstract long AllocatedLength
{
get;
set;
}
public abstract long StartVcn
{
get;
}
public abstract long DataLength
{
get;
set;
}
public abstract long InitializedDataLength
{
get;
set;
}
public bool IsNonResident
{
get { return _nonResidentFlag != 0; }
}
public string Name
{
get { return _name; }
}
public AttributeFlags Flags
{
get { return _flags; }
set { _flags = value; }
}
public abstract int Size { get; }
public static AttributeRecord FromBytes(byte[] buffer, int offset, out int length)
{
if (Utilities.ToUInt32LittleEndian(buffer, offset) == 0xFFFFFFFF)
{
length = 0;
return null;
}
else if (buffer[offset + 0x08] != 0x00)
{
return new NonResidentAttributeRecord(buffer, offset, out length);
}
else
{
return new ResidentAttributeRecord(buffer, offset, out length);
}
}
public static int CompareStartVcns(AttributeRecord x, AttributeRecord y)
{
if (x.StartVcn < y.StartVcn)
{
return -1;
}
else if (x.StartVcn == y.StartVcn)
{
return 0;
}
else
{
return 1;
}
}
public abstract Range<long, long>[] GetClusters();
public abstract IBuffer GetReadOnlyDataBuffer(INtfsContext context);
public int CompareTo(AttributeRecord other)
{
int val = ((int)_type) - (int)other._type;
if (val != 0)
{
return val;
}
val = string.Compare(_name, other._name, StringComparison.OrdinalIgnoreCase);
if (val != 0)
{
return val;
}
return ((int)_attributeId) - (int)other._attributeId;
}
public abstract int Write(byte[] buffer, int offset);
public virtual void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "ATTRIBUTE RECORD");
writer.WriteLine(indent + " Type: " + _type);
writer.WriteLine(indent + " Non-Resident: " + _nonResidentFlag);
writer.WriteLine(indent + " Name: " + _name);
writer.WriteLine(indent + " Flags: " + _flags);
writer.WriteLine(indent + " AttributeId: " + _attributeId);
}
protected virtual void Read(byte[] buffer, int offset, out int length)
{
_type = (AttributeType)Utilities.ToUInt32LittleEndian(buffer, offset + 0x00);
length = Utilities.ToInt32LittleEndian(buffer, offset + 0x04);
_nonResidentFlag = buffer[offset + 0x08];
byte nameLength = buffer[offset + 0x09];
ushort nameOffset = Utilities.ToUInt16LittleEndian(buffer, offset + 0x0A);
_flags = (AttributeFlags)Utilities.ToUInt16LittleEndian(buffer, offset + 0x0C);
_attributeId = Utilities.ToUInt16LittleEndian(buffer, offset + 0x0E);
if (nameLength != 0x00)
{
if (nameLength + nameOffset > length)
{
throw new IOException("Corrupt attribute, name outside of attribute");
}
_name = Encoding.Unicode.GetString(buffer, offset + nameOffset, nameLength * 2);
}
}
}
}
+130
View File
@@ -0,0 +1,130 @@
//
// 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;
/// <summary>
/// Fully-qualified reference to an attribute.
/// </summary>
internal class AttributeReference : IComparable<AttributeReference>, IEquatable<AttributeReference>
{
private FileRecordReference _fileReference;
private ushort _attributeId;
/// <summary>
/// Initializes a new instance of the AttributeReference class.
/// </summary>
/// <param name="fileReference">The file containing the attribute.</param>
/// <param name="attributeId">The identity of the attribute within the file record.</param>
public AttributeReference(FileRecordReference fileReference, ushort attributeId)
{
_fileReference = fileReference;
_attributeId = attributeId;
}
/// <summary>
/// Gets the file containing the attribute.
/// </summary>
public FileRecordReference File
{
get { return _fileReference; }
}
/// <summary>
/// Gets the identity of the attribute within the file record.
/// </summary>
public ushort AttributeId
{
get { return _attributeId; }
}
/// <summary>
/// The reference as a string.
/// </summary>
/// <returns>String representing the attribute.</returns>
public override string ToString()
{
return _fileReference.ToString() + ".attr[" + _attributeId + "]";
}
#region IComparable<AttributeReference> Members
/// <summary>
/// Compares this attribute reference to another.
/// </summary>
/// <param name="other">The attribute reference to compare against.</param>
/// <returns>Zero if references are identical.</returns>
public int CompareTo(AttributeReference other)
{
int refDiff = _fileReference.CompareTo(other._fileReference);
if (refDiff != 0)
{
return refDiff;
}
return _attributeId.CompareTo(other._attributeId);
}
#endregion
#region IEquatable<AttributeReference> Members
/// <summary>
/// Indicates if two references are equivalent.
/// </summary>
/// <param name="other">The attribute reference to compare.</param>
/// <returns><c>true</c> if the references are equivalent.</returns>
public bool Equals(AttributeReference other)
{
return CompareTo(other) == 0;
}
#endregion
/// <summary>
/// Indicates if this reference is equivalent to another object.
/// </summary>
/// <param name="obj">The object to compare.</param>
/// <returns><c>true</c> if obj is an equivalent attribute reference.</returns>
public override bool Equals(object obj)
{
AttributeReference objAsAttrRef = obj as AttributeReference;
if (objAsAttrRef == null)
{
return false;
}
return Equals(objAsAttrRef);
}
/// <summary>
/// Gets the hash code for this reference.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return _fileReference.GetHashCode() ^ _attributeId.GetHashCode();
}
}
}
+116
View File
@@ -0,0 +1,116 @@
//
// 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
{
/// <summary>
/// Enumeration of NTFS file attribute types.
/// </summary>
/// <remarks>Normally applications only create Data attributes.</remarks>
public enum AttributeType : int
{
/// <summary>
/// No type specified.
/// </summary>
None = 0x00,
/// <summary>
/// NTFS Standard Information.
/// </summary>
StandardInformation = 0x10,
/// <summary>
/// Attribute list, that holds a list of attribute locations for files with a large attribute set.
/// </summary>
AttributeList = 0x20,
/// <summary>
/// FileName information, one per hard link.
/// </summary>
FileName = 0x30,
/// <summary>
/// Distributed Link Tracking object identity.
/// </summary>
ObjectId = 0x40,
/// <summary>
/// Legacy Security Descriptor attribute.
/// </summary>
SecurityDescriptor = 0x50,
/// <summary>
/// The name of the NTFS volume.
/// </summary>
VolumeName = 0x60,
/// <summary>
/// Information about the NTFS volume.
/// </summary>
VolumeInformation = 0x70,
/// <summary>
/// File contents, a file may have multiple data attributes (default is unnamed).
/// </summary>
Data = 0x80,
/// <summary>
/// Root information for directories and other NTFS index's.
/// </summary>
IndexRoot = 0x90,
/// <summary>
/// For 'large' directories and other NTFS index's, the index contents.
/// </summary>
IndexAllocation = 0xA0,
/// <summary>
/// Bitmask of allocated clusters, records, etc - typically used in indexes.
/// </summary>
Bitmap = 0xB0,
/// <summary>
/// ReparsePoint information.
/// </summary>
ReparsePoint = 0xC0,
/// <summary>
/// Extended Attributes meta-information.
/// </summary>
ExtendedAttributesInformation = 0xD0,
/// <summary>
/// Extended Attributes data.
/// </summary>
ExtendedAttributes = 0xE0,
/// <summary>
/// Legacy attribute type from NT (not used).
/// </summary>
PropertySet = 0xF0,
/// <summary>
/// Encrypted File System (EFS) data.
/// </summary>
LoggedUtilityStream = 0x100
}
}
+219
View File
@@ -0,0 +1,219 @@
//
// 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.Globalization;
using System.IO;
internal class BiosParameterBlock
{
public string OemId;
public ushort BytesPerSector;
public byte SectorsPerCluster;
public ushort ReservedSectors; // Must be 0
public byte NumFats; // Must be 0
public ushort FatRootEntriesCount; // Must be 0
public ushort TotalSectors16; // Must be 0
public byte Media; // Must be 0xF8
public ushort FatSize16; // Must be 0
public ushort SectorsPerTrack; // Value: 0x3F 0x00
public ushort NumHeads; // Value: 0xFF 0x00
public uint HiddenSectors; // Value: 0x3F 0x00 0x00 0x00
public uint TotalSectors32; // Must be 0
public byte BiosDriveNumber; // Value: 0x80 (first hard disk)
public byte ChkDskFlags; // Value: 0x00
public byte SignatureByte; // Value: 0x80
public byte PaddingByte; // Value: 0x00
public long TotalSectors64;
public long MftCluster;
public long MftMirrorCluster;
public byte RawMftRecordSize;
public byte RawIndexBufferSize;
public ulong VolumeSerialNumber;
public int MftRecordSize
{
get { return CalcRecordSize(RawMftRecordSize); }
}
public int IndexBufferSize
{
get { return CalcRecordSize(RawIndexBufferSize); }
}
public int BytesPerCluster
{
get { return ((int)BytesPerSector) * ((int)SectorsPerCluster); }
}
public void Dump(TextWriter writer, string linePrefix)
{
writer.WriteLine(linePrefix + "BIOS PARAMETER BLOCK (BPB)");
writer.WriteLine(linePrefix + " OEM ID: " + OemId);
writer.WriteLine(linePrefix + " Bytes per Sector: " + BytesPerSector);
writer.WriteLine(linePrefix + " Sectors per Cluster: " + SectorsPerCluster);
writer.WriteLine(linePrefix + " Reserved Sectors: " + ReservedSectors);
writer.WriteLine(linePrefix + " # FATs: " + NumFats);
writer.WriteLine(linePrefix + " # FAT Root Entries: " + FatRootEntriesCount);
writer.WriteLine(linePrefix + " Total Sectors (16b): " + TotalSectors16);
writer.WriteLine(linePrefix + " Media: " + Media.ToString("X", CultureInfo.InvariantCulture) + "h");
writer.WriteLine(linePrefix + " FAT size (16b): " + FatSize16);
writer.WriteLine(linePrefix + " Sectors per Track: " + SectorsPerTrack);
writer.WriteLine(linePrefix + " # Heads: " + NumHeads);
writer.WriteLine(linePrefix + " Hidden Sectors: " + HiddenSectors);
writer.WriteLine(linePrefix + " Total Sectors (32b): " + TotalSectors32);
writer.WriteLine(linePrefix + " BIOS Drive Number: " + BiosDriveNumber);
writer.WriteLine(linePrefix + " Chkdsk Flags: " + ChkDskFlags);
writer.WriteLine(linePrefix + " Signature Byte: " + SignatureByte);
writer.WriteLine(linePrefix + " Total Sectors (64b): " + TotalSectors64);
writer.WriteLine(linePrefix + " MFT Record Size: " + RawMftRecordSize);
writer.WriteLine(linePrefix + " Index Buffer Size: " + RawIndexBufferSize);
writer.WriteLine(linePrefix + " Volume Serial Number: " + VolumeSerialNumber);
}
internal static BiosParameterBlock Initialized(Geometry diskGeometry, int clusterSize, uint partitionStartLba, long partitionSizeLba, int mftRecordSize, int indexBufferSize)
{
BiosParameterBlock bpb = new BiosParameterBlock();
bpb.OemId = "NTFS ";
bpb.BytesPerSector = Sizes.Sector;
bpb.SectorsPerCluster = (byte)(clusterSize / bpb.BytesPerSector);
bpb.ReservedSectors = 0;
bpb.NumFats = 0;
bpb.FatRootEntriesCount = 0;
bpb.TotalSectors16 = 0;
bpb.Media = 0xF8;
bpb.FatSize16 = 0;
bpb.SectorsPerTrack = (ushort)diskGeometry.SectorsPerTrack;
bpb.NumHeads = (ushort)diskGeometry.HeadsPerCylinder;
bpb.HiddenSectors = partitionStartLba;
bpb.TotalSectors32 = 0;
bpb.BiosDriveNumber = 0x80;
bpb.ChkDskFlags = 0;
bpb.SignatureByte = 0x80;
bpb.PaddingByte = 0;
bpb.TotalSectors64 = partitionSizeLba - 1;
bpb.RawMftRecordSize = bpb.CodeRecordSize(mftRecordSize);
bpb.RawIndexBufferSize = bpb.CodeRecordSize(indexBufferSize);
bpb.VolumeSerialNumber = GenSerialNumber();
return bpb;
}
internal static BiosParameterBlock FromBytes(byte[] bytes, int offset)
{
BiosParameterBlock bpb = new BiosParameterBlock();
bpb.OemId = Utilities.BytesToString(bytes, offset + 0x03, 8);
bpb.BytesPerSector = Utilities.ToUInt16LittleEndian(bytes, offset + 0x0B);
bpb.SectorsPerCluster = bytes[offset + 0x0D];
bpb.ReservedSectors = Utilities.ToUInt16LittleEndian(bytes, offset + 0x0E);
bpb.NumFats = bytes[offset + 0x10];
bpb.FatRootEntriesCount = Utilities.ToUInt16LittleEndian(bytes, offset + 0x11);
bpb.TotalSectors16 = Utilities.ToUInt16LittleEndian(bytes, offset + 0x13);
bpb.Media = bytes[offset + 0x15];
bpb.FatSize16 = Utilities.ToUInt16LittleEndian(bytes, offset + 0x16);
bpb.SectorsPerTrack = Utilities.ToUInt16LittleEndian(bytes, offset + 0x18);
bpb.NumHeads = Utilities.ToUInt16LittleEndian(bytes, offset + 0x1A);
bpb.HiddenSectors = Utilities.ToUInt32LittleEndian(bytes, offset + 0x1C);
bpb.TotalSectors32 = Utilities.ToUInt32LittleEndian(bytes, offset + 0x20);
bpb.BiosDriveNumber = bytes[offset + 0x24];
bpb.ChkDskFlags = bytes[offset + 0x25];
bpb.SignatureByte = bytes[offset + 0x26];
bpb.PaddingByte = bytes[offset + 0x27];
bpb.TotalSectors64 = Utilities.ToInt64LittleEndian(bytes, offset + 0x28);
bpb.MftCluster = Utilities.ToInt64LittleEndian(bytes, offset + 0x30);
bpb.MftMirrorCluster = Utilities.ToInt64LittleEndian(bytes, offset + 0x38);
bpb.RawMftRecordSize = bytes[offset + 0x40];
bpb.RawIndexBufferSize = bytes[offset + 0x44];
bpb.VolumeSerialNumber = Utilities.ToUInt64LittleEndian(bytes, offset + 0x48);
return bpb;
}
internal void ToBytes(byte[] buffer, int offset)
{
Utilities.StringToBytes(OemId, buffer, offset + 0x03, 8);
Utilities.WriteBytesLittleEndian(BytesPerSector, buffer, offset + 0x0B);
buffer[offset + 0x0D] = SectorsPerCluster;
Utilities.WriteBytesLittleEndian(ReservedSectors, buffer, offset + 0x0E);
buffer[offset + 0x10] = NumFats;
Utilities.WriteBytesLittleEndian(FatRootEntriesCount, buffer, offset + 0x11);
Utilities.WriteBytesLittleEndian(TotalSectors16, buffer, offset + 0x13);
buffer[offset + 0x15] = Media;
Utilities.WriteBytesLittleEndian(FatSize16, buffer, offset + 0x16);
Utilities.WriteBytesLittleEndian(SectorsPerTrack, buffer, offset + 0x18);
Utilities.WriteBytesLittleEndian(NumHeads, buffer, offset + 0x1A);
Utilities.WriteBytesLittleEndian(HiddenSectors, buffer, offset + 0x1C);
Utilities.WriteBytesLittleEndian(TotalSectors32, buffer, offset + 0x20);
buffer[offset + 0x24] = BiosDriveNumber;
buffer[offset + 0x25] = ChkDskFlags;
buffer[offset + 0x26] = SignatureByte;
buffer[offset + 0x27] = PaddingByte;
Utilities.WriteBytesLittleEndian(TotalSectors64, buffer, offset + 0x28);
Utilities.WriteBytesLittleEndian(MftCluster, buffer, offset + 0x30);
Utilities.WriteBytesLittleEndian(MftMirrorCluster, buffer, offset + 0x38);
buffer[offset + 0x40] = RawMftRecordSize;
buffer[offset + 0x44] = RawIndexBufferSize;
Utilities.WriteBytesLittleEndian(VolumeSerialNumber, buffer, offset + 0x48);
}
internal int CalcRecordSize(byte rawSize)
{
if ((rawSize & 0x80) != 0)
{
return 1 << (-(sbyte)rawSize);
}
else
{
return rawSize * SectorsPerCluster * BytesPerSector;
}
}
private static ulong GenSerialNumber()
{
byte[] buffer = new byte[8];
Random rng = new Random();
rng.NextBytes(buffer);
return Utilities.ToUInt64LittleEndian(buffer, 0);
}
private byte CodeRecordSize(int size)
{
if (size >= BytesPerCluster)
{
return (byte)(size / BytesPerCluster);
}
else
{
sbyte val = 0;
while (size != 1)
{
size = (size >> 1) & 0x7FFFFFFF;
val++;
}
return (byte)-val;
}
}
}
}
+223
View File
@@ -0,0 +1,223 @@
//
// 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.IO;
internal sealed class Bitmap : IDisposable
{
private Stream _stream;
private long _maxIndex;
private BlockCacheStream _bitmap;
private long _nextAvailable;
public Bitmap(Stream stream, long maxIndex)
{
_stream = stream;
_maxIndex = maxIndex;
_bitmap = new BlockCacheStream(SparseStream.FromStream(stream, Ownership.None), Ownership.None);
}
public void Dispose()
{
if (_bitmap != null)
{
_bitmap.Dispose();
_bitmap = null;
}
}
public bool IsPresent(long index)
{
long byteIdx = index / 8;
int mask = 1 << (int)(index % 8);
return (GetByte(byteIdx) & mask) != 0;
}
public void MarkPresent(long index)
{
long byteIdx = index / 8;
byte mask = (byte)(1 << (byte)(index % 8));
if (byteIdx >= _bitmap.Length)
{
_bitmap.Position = Utilities.RoundUp(byteIdx + 1, 8) - 1;
_bitmap.WriteByte(0);
}
SetByte(byteIdx, (byte)(GetByte(byteIdx) | mask));
}
public void MarkPresentRange(long index, long count)
{
if (count <= 0)
{
return;
}
long firstByte = index / 8;
long lastByte = (index + count - 1) / 8;
if (lastByte >= _bitmap.Length)
{
_bitmap.Position = Utilities.RoundUp(lastByte + 1, 8) - 1;
_bitmap.WriteByte(0);
}
byte[] buffer = new byte[lastByte - firstByte + 1];
buffer[0] = GetByte(firstByte);
if (buffer.Length != 1)
{
buffer[buffer.Length - 1] = GetByte(lastByte);
}
for (long i = index; i < index + count; ++i)
{
long byteIdx = (i / 8) - firstByte;
byte mask = (byte)(1 << (byte)(i % 8));
buffer[byteIdx] |= mask;
}
SetBytes(firstByte, buffer);
}
public void MarkAbsent(long index)
{
long byteIdx = index / 8;
byte mask = (byte)(1 << (byte)(index % 8));
if (byteIdx < _stream.Length)
{
SetByte(byteIdx, (byte)(GetByte(byteIdx) & ~mask));
}
if (index < _nextAvailable)
{
_nextAvailable = index;
}
}
internal void MarkAbsentRange(long index, long count)
{
if (count <= 0)
{
return;
}
long firstByte = index / 8;
long lastByte = (index + count - 1) / 8;
if (lastByte >= _bitmap.Length)
{
_bitmap.Position = Utilities.RoundUp(lastByte + 1, 8) - 1;
_bitmap.WriteByte(0);
}
byte[] buffer = new byte[lastByte - firstByte + 1];
buffer[0] = GetByte(firstByte);
if (buffer.Length != 1)
{
buffer[buffer.Length - 1] = GetByte(lastByte);
}
for (long i = index; i < index + count; ++i)
{
long byteIdx = (i / 8) - firstByte;
byte mask = (byte)(1 << (byte)(i % 8));
buffer[byteIdx] &= (byte)(~mask);
}
SetBytes(firstByte, buffer);
if (index < _nextAvailable)
{
_nextAvailable = index;
}
}
internal long AllocateFirstAvailable(long minValue)
{
long i = Math.Max(minValue, _nextAvailable);
while (IsPresent(i) && i < _maxIndex)
{
++i;
}
if (i < _maxIndex)
{
MarkPresent(i);
_nextAvailable = i + 1;
return i;
}
else
{
return -1;
}
}
internal long SetTotalEntries(long numEntries)
{
long length = Utilities.RoundUp(Utilities.Ceil(numEntries, 8), 8);
_stream.SetLength(length);
return length * 8;
}
private byte GetByte(long index)
{
if (index >= _bitmap.Length)
{
return 0;
}
byte[] buffer = new byte[1];
_bitmap.Position = index;
if (_bitmap.Read(buffer, 0, 1) != 0)
{
return buffer[0];
}
else
{
return 0;
}
}
private void SetByte(long index, byte value)
{
byte[] buffer = new byte[] { value };
_bitmap.Position = index;
_bitmap.Write(buffer, 0, 1);
_bitmap.Flush();
}
private void SetBytes(long index, byte[] buffer)
{
_bitmap.Position = index;
_bitmap.Write(buffer, 0, buffer.Length);
_bitmap.Flush();
}
}
}
+270
View File
@@ -0,0 +1,270 @@
//
// 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.Collections.Generic;
using System.IO;
internal class ClusterBitmap : System.IDisposable
{
private File _file;
private Bitmap _bitmap;
private long _nextDataCluster;
private bool _fragmentedDiskMode;
public ClusterBitmap(File file)
{
_file = file;
_bitmap = new Bitmap(
_file.OpenStream(AttributeType.Data, null, FileAccess.ReadWrite),
Utilities.Ceil(file.Context.BiosParameterBlock.TotalSectors64, file.Context.BiosParameterBlock.SectorsPerCluster));
}
public void Dispose()
{
if (_bitmap != null)
{
_bitmap.Dispose();
_bitmap = null;
}
}
/// <summary>
/// Allocates clusters from the disk.
/// </summary>
/// <param name="count">The number of clusters to allocate.</param>
/// <param name="proposedStart">The proposed start cluster (or -1).</param>
/// <param name="isMft"><c>true</c> if this attribute is the $MFT\$DATA attribute.</param>
/// <param name="total">The total number of clusters in the file, including this allocation.</param>
/// <returns>The list of cluster allocations.</returns>
public Tuple<long, long>[] AllocateClusters(long count, long proposedStart, bool isMft, long total)
{
List<Tuple<long, long>> result = new List<Tuple<long, long>>();
long numFound = 0;
long totalClusters = _file.Context.RawStream.Length / _file.Context.BiosParameterBlock.BytesPerCluster;
if (isMft)
{
// First, try to extend the existing cluster run (if available)
if (proposedStart >= 0)
{
numFound += ExtendRun(count - numFound, result, proposedStart, totalClusters);
}
// The MFT grows sequentially across the disk
if (numFound < count && !_fragmentedDiskMode)
{
numFound += FindClusters(count - numFound, result, 0, totalClusters, isMft, true, 0);
}
if (numFound < count)
{
numFound += FindClusters(count - numFound, result, 0, totalClusters, isMft, false, 0);
}
}
else
{
// First, try to extend the existing cluster run (if available)
if (proposedStart >= 0)
{
numFound += ExtendRun(count - numFound, result, proposedStart, totalClusters);
}
// Try to find a contiguous range
if (numFound < count && !_fragmentedDiskMode)
{
numFound += FindClusters(count - numFound, result, totalClusters / 8, totalClusters, isMft, true, total / 4);
}
if (numFound < count)
{
numFound += FindClusters(count - numFound, result, totalClusters / 8, totalClusters, isMft, false, 0);
}
if (numFound < count)
{
numFound = FindClusters(count - numFound, result, totalClusters / 16, totalClusters / 8, isMft, false, 0);
}
if (numFound < count)
{
numFound = FindClusters(count - numFound, result, totalClusters / 32, totalClusters / 16, isMft, false, 0);
}
if (numFound < count)
{
numFound = FindClusters(count - numFound, result, 0, totalClusters / 32, isMft, false, 0);
}
}
if (numFound < count)
{
FreeClusters(result.ToArray());
throw new IOException("Out of disk space");
}
// If we found more than two clusters, or we have a fragmented result,
// then switch out of trying to allocate contiguous ranges. Similarly,
// switch back if we found a resonable quantity in a single span.
if ((numFound > 4 && result.Count == 1) || result.Count > 1)
{
_fragmentedDiskMode = (numFound / result.Count) < 4;
}
return result.ToArray();
}
internal void MarkAllocated(long first, long count)
{
_bitmap.MarkPresentRange(first, count);
}
internal void FreeClusters(params Tuple<long, long>[] runs)
{
foreach (var run in runs)
{
_bitmap.MarkAbsentRange(run.First, run.Second);
}
}
internal void FreeClusters(params Range<long, long>[] runs)
{
foreach (var run in runs)
{
_bitmap.MarkAbsentRange(run.Offset, run.Count);
}
}
/// <summary>
/// Sets the total number of clusters managed in the volume.
/// </summary>
/// <param name="numClusters">Total number of clusters in the volume.</param>
/// <remarks>
/// Any clusters represented in the bitmap beyond the total number in the volume are marked as in-use.
/// </remarks>
internal void SetTotalClusters(long numClusters)
{
long actualClusters = _bitmap.SetTotalEntries(numClusters);
if (actualClusters != numClusters)
{
MarkAllocated(numClusters, actualClusters - numClusters);
}
}
private long ExtendRun(long count, List<Tuple<long, long>> result, long start, long end)
{
long focusCluster = start;
while (!_bitmap.IsPresent(focusCluster) && focusCluster < end && focusCluster - start < count)
{
++focusCluster;
}
long numFound = focusCluster - start;
if (numFound > 0)
{
_bitmap.MarkPresentRange(start, numFound);
result.Add(new Tuple<long, long>(start, numFound));
}
return numFound;
}
/// <summary>
/// Finds one or more free clusters in a range.
/// </summary>
/// <param name="count">The number of clusters required.</param>
/// <param name="result">The list of clusters found (i.e. out param).</param>
/// <param name="start">The first cluster in the range to look at.</param>
/// <param name="end">The last cluster in the range to look at (exclusive).</param>
/// <param name="isMft">Indicates if the clusters are for the MFT.</param>
/// <param name="contiguous">Indicates if contiguous clusters are required.</param>
/// <param name="headroom">Indicates how many clusters to skip before next allocation, to prevent fragmentation.</param>
/// <returns>The number of clusters found in the range.</returns>
private long FindClusters(long count, List<Tuple<long, long>> result, long start, long end, bool isMft, bool contiguous, long headroom)
{
long numFound = 0;
long focusCluster;
if (isMft)
{
focusCluster = start;
}
else
{
if (_nextDataCluster < start || _nextDataCluster >= end)
{
_nextDataCluster = start;
}
focusCluster = _nextDataCluster;
}
long numInspected = 0;
while (numFound < count && focusCluster >= start && numInspected < end - start)
{
if (!_bitmap.IsPresent(focusCluster))
{
// Start of a run...
long runStart = focusCluster;
++focusCluster;
while (!_bitmap.IsPresent(focusCluster) && focusCluster - runStart < (count - numFound))
{
++focusCluster;
++numInspected;
}
if (!contiguous || (focusCluster - runStart) == (count - numFound))
{
_bitmap.MarkPresentRange(runStart, focusCluster - runStart);
result.Add(new Tuple<long, long>(runStart, focusCluster - runStart));
numFound += focusCluster - runStart;
}
}
else
{
++focusCluster;
}
++numInspected;
if (focusCluster >= end)
{
focusCluster = start;
}
}
if (!isMft)
{
_nextDataCluster = focusCluster + headroom;
}
return numFound;
}
}
}
+51
View File
@@ -0,0 +1,51 @@
//
// 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.Collections.Generic;
internal abstract class ClusterStream
{
public abstract long AllocatedClusterCount
{
get;
}
public abstract IEnumerable<Range<long, long>> StoredClusters
{
get;
}
public abstract bool IsClusterStored(long vcn);
public abstract void ExpandToClusters(long numVirtualClusters, NonResidentAttributeRecord extent, bool allocate);
public abstract void TruncateToClusters(long numVirtualClusters);
public abstract void ReadClusters(long startVcn, int count, byte[] buffer, int offset);
public abstract int WriteClusters(long startVcn, int count, byte[] buffer, int offset);
public abstract int ClearClusters(long startVcn, int count);
}
}
+257
View File
@@ -0,0 +1,257 @@
//
// 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.IO;
using DiscUtils.Compression;
internal sealed class CompressedClusterStream : ClusterStream
{
private INtfsContext _context;
private NtfsAttribute _attr;
private RawClusterStream _rawStream;
private int _bytesPerCluster;
private byte[] _cacheBuffer;
private long _cacheBufferVcn = -1;
private byte[] _ioBuffer;
public CompressedClusterStream(INtfsContext context, NtfsAttribute attr, RawClusterStream rawStream)
{
_context = context;
_attr = attr;
_rawStream = rawStream;
_bytesPerCluster = _context.BiosParameterBlock.BytesPerCluster;
_cacheBuffer = new byte[_attr.CompressionUnitSize * context.BiosParameterBlock.BytesPerCluster];
_ioBuffer = new byte[_attr.CompressionUnitSize * context.BiosParameterBlock.BytesPerCluster];
}
public override long AllocatedClusterCount
{
get { return _rawStream.AllocatedClusterCount; }
}
public override IEnumerable<Range<long, long>> StoredClusters
{
get
{
return Range<long, long>.Chunked(_rawStream.StoredClusters, _attr.CompressionUnitSize);
}
}
public override bool IsClusterStored(long vcn)
{
return _rawStream.IsClusterStored(CompressionStart(vcn));
}
public override void ExpandToClusters(long numVirtualClusters, NonResidentAttributeRecord extent, bool allocate)
{
_rawStream.ExpandToClusters(Utilities.RoundUp(numVirtualClusters, _attr.CompressionUnitSize), extent, false);
}
public override void TruncateToClusters(long numVirtualClusters)
{
long alignedNum = Utilities.RoundUp(numVirtualClusters, _attr.CompressionUnitSize);
_rawStream.TruncateToClusters(alignedNum);
if (alignedNum != numVirtualClusters)
{
_rawStream.ReleaseClusters(numVirtualClusters, (int)(alignedNum - numVirtualClusters));
}
}
public override void ReadClusters(long startVcn, int count, byte[] buffer, int offset)
{
if (buffer.Length < (count * _bytesPerCluster) + offset)
{
throw new ArgumentException("Cluster buffer too small", "buffer");
}
int totalRead = 0;
while (totalRead < count)
{
long focusVcn = startVcn + totalRead;
LoadCache(focusVcn);
int cacheOffset = (int)(focusVcn - _cacheBufferVcn);
int toCopy = Math.Min(_attr.CompressionUnitSize - cacheOffset, count - totalRead);
Array.Copy(_cacheBuffer, cacheOffset * _bytesPerCluster, buffer, offset + (totalRead * _bytesPerCluster), toCopy * _bytesPerCluster);
totalRead += toCopy;
}
}
public override int WriteClusters(long startVcn, int count, byte[] buffer, int offset)
{
if (buffer.Length < (count * _bytesPerCluster) + offset)
{
throw new ArgumentException("Cluster buffer too small", "buffer");
}
int totalAllocated = 0;
int totalWritten = 0;
while (totalWritten < count)
{
long focusVcn = startVcn + totalWritten;
long cuStart = CompressionStart(focusVcn);
if (cuStart == focusVcn && count - totalWritten >= _attr.CompressionUnitSize)
{
// Aligned write...
totalAllocated += CompressAndWriteClusters(focusVcn, _attr.CompressionUnitSize, buffer, offset + (totalWritten * _bytesPerCluster));
totalWritten += _attr.CompressionUnitSize;
}
else
{
// Unaligned, so go through cache
LoadCache(focusVcn);
int cacheOffset = (int)(focusVcn - _cacheBufferVcn);
int toCopy = Math.Min(count - totalWritten, _attr.CompressionUnitSize - cacheOffset);
Array.Copy(buffer, offset + (totalWritten * _bytesPerCluster), _cacheBuffer, cacheOffset * _bytesPerCluster, toCopy * _bytesPerCluster);
totalAllocated += CompressAndWriteClusters(_cacheBufferVcn, _attr.CompressionUnitSize, _cacheBuffer, 0);
totalWritten += toCopy;
}
}
return totalAllocated;
}
public override int ClearClusters(long startVcn, int count)
{
int totalReleased = 0;
int totalCleared = 0;
while (totalCleared < count)
{
long focusVcn = startVcn + totalCleared;
if (CompressionStart(focusVcn) == focusVcn && count - totalCleared >= _attr.CompressionUnitSize)
{
// Aligned - so it's a sparse compression unit...
totalReleased += _rawStream.ReleaseClusters(startVcn, _attr.CompressionUnitSize);
totalCleared += _attr.CompressionUnitSize;
}
else
{
int toZero = (int)Math.Min(count - totalCleared, _attr.CompressionUnitSize - (focusVcn - CompressionStart(focusVcn)));
totalReleased -= WriteZeroClusters(focusVcn, toZero);
totalCleared += toZero;
}
}
return totalReleased;
}
private int WriteZeroClusters(long focusVcn, int count)
{
int allocatedClusters = 0;
byte[] zeroBuffer = new byte[16 * _bytesPerCluster];
int numWritten = 0;
while (numWritten < count)
{
int toWrite = Math.Min(count - numWritten, 16);
allocatedClusters += WriteClusters(focusVcn + numWritten, toWrite, zeroBuffer, 0);
numWritten += toWrite;
}
return allocatedClusters;
}
private int CompressAndWriteClusters(long focusVcn, int count, byte[] buffer, int offset)
{
BlockCompressor compressor = _context.Options.Compressor;
compressor.BlockSize = _bytesPerCluster;
int totalAllocated = 0;
int compressedLength = _ioBuffer.Length;
var result = compressor.Compress(buffer, offset, _attr.CompressionUnitSize * _bytesPerCluster, _ioBuffer, 0, ref compressedLength);
if (result == CompressionResult.AllZeros)
{
totalAllocated -= _rawStream.ReleaseClusters(focusVcn, count);
}
else if (result == CompressionResult.Compressed && (_attr.CompressionUnitSize * _bytesPerCluster) - compressedLength > _bytesPerCluster)
{
int compClusters = Utilities.Ceil(compressedLength, _bytesPerCluster);
totalAllocated += _rawStream.AllocateClusters(focusVcn, compClusters);
totalAllocated += _rawStream.WriteClusters(focusVcn, compClusters, _ioBuffer, 0);
totalAllocated -= _rawStream.ReleaseClusters(focusVcn + compClusters, _attr.CompressionUnitSize - compClusters);
}
else
{
totalAllocated += _rawStream.AllocateClusters(focusVcn, _attr.CompressionUnitSize);
totalAllocated += _rawStream.WriteClusters(focusVcn, _attr.CompressionUnitSize, buffer, offset);
}
return totalAllocated;
}
private long CompressionStart(long vcn)
{
return Utilities.RoundDown(vcn, _attr.CompressionUnitSize);
}
private void LoadCache(long vcn)
{
long cuStart = CompressionStart(vcn);
if (_cacheBufferVcn != cuStart)
{
if (_rawStream.AreAllClustersStored(cuStart, _attr.CompressionUnitSize))
{
// Uncompressed data - read straight into cache buffer
_rawStream.ReadClusters(cuStart, _attr.CompressionUnitSize, _cacheBuffer, 0);
}
else if (_rawStream.IsClusterStored(cuStart))
{
// Compressed data - read via IO buffer
_rawStream.ReadClusters(cuStart, _attr.CompressionUnitSize, _ioBuffer, 0);
int expected = (int)Math.Min(_attr.Length - (vcn * _bytesPerCluster), _attr.CompressionUnitSize * _bytesPerCluster);
int decomp = _context.Options.Compressor.Decompress(_ioBuffer, 0, _ioBuffer.Length, _cacheBuffer, 0);
if (decomp < expected)
{
throw new IOException("Decompression returned too little data");
}
}
else
{
// Sparse, wipe cache buffer directly
Array.Clear(_cacheBuffer, 0, _cacheBuffer.Length);
}
_cacheBufferVcn = cuStart;
}
}
}
}
+85
View File
@@ -0,0 +1,85 @@
//
// 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;
internal class CookedDataRun
{
private long _startVcn;
private long _startLcn;
private DataRun _raw;
private NonResidentAttributeRecord _attributeExtent;
public CookedDataRun(DataRun raw, long startVcn, long prevLcn, NonResidentAttributeRecord attributeExtent)
{
_raw = raw;
_startVcn = startVcn;
_startLcn = prevLcn + raw.RunOffset;
_attributeExtent = attributeExtent;
if (startVcn < 0)
{
throw new ArgumentOutOfRangeException("startVcn", startVcn, "VCN must be >= 0");
}
if (_startLcn < 0)
{
throw new ArgumentOutOfRangeException("prevLcn", prevLcn, "LCN must be >= 0");
}
}
public long StartVcn
{
get { return _startVcn; }
}
public long StartLcn
{
get { return _startLcn; }
set { _startLcn = value; }
}
public long Length
{
get { return _raw.RunLength; }
set { _raw.RunLength = value; }
}
public bool IsSparse
{
get { return _raw.IsSparse; }
}
public DataRun DataRun
{
get { return _raw; }
}
public NonResidentAttributeRecord AttributeExtent
{
get { return _attributeExtent; }
}
}
}
+321
View File
@@ -0,0 +1,321 @@
//
// 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.IO;
internal class CookedDataRuns
{
private List<CookedDataRun> _runs;
private int _firstDirty = int.MaxValue;
private int _lastDirty = 0;
public CookedDataRuns()
{
_runs = new List<CookedDataRun>();
}
public CookedDataRuns(IEnumerable<DataRun> rawRuns, NonResidentAttributeRecord attributeExtent)
{
_runs = new List<CookedDataRun>();
Append(rawRuns, attributeExtent);
}
public long NextVirtualCluster
{
get
{
if (_runs.Count == 0)
{
return 0;
}
else
{
int lastRun = _runs.Count - 1;
return _runs[lastRun].StartVcn + _runs[lastRun].Length;
}
}
}
public CookedDataRun Last
{
get
{
if (_runs.Count == 0)
{
return null;
}
else
{
return _runs[_runs.Count - 1];
}
}
}
public int Count
{
get { return _runs.Count; }
}
public CookedDataRun this[int index]
{
get { return _runs[index]; }
}
public int FindDataRun(long vcn, int startIdx)
{
int numRuns = _runs.Count;
if (numRuns > 0)
{
CookedDataRun run = _runs[numRuns - 1];
if (vcn >= run.StartVcn)
{
if (run.StartVcn + run.Length > vcn)
{
return numRuns - 1;
}
else
{
throw new IOException("Looking for VCN outside of data runs");
}
}
for (int i = startIdx; i < numRuns; ++i)
{
run = _runs[i];
if (run.StartVcn + run.Length > vcn)
{
return i;
}
}
}
throw new IOException("Looking for VCN outside of data runs");
}
public void Append(DataRun rawRun, NonResidentAttributeRecord attributeExtent)
{
CookedDataRun last = Last;
_runs.Add(new CookedDataRun(rawRun, NextVirtualCluster, last == null ? 0 : last.StartLcn, attributeExtent));
}
public void Append(IEnumerable<DataRun> rawRuns, NonResidentAttributeRecord attributeExtent)
{
long vcn = NextVirtualCluster;
long lcn = 0;
foreach (var run in rawRuns)
{
_runs.Add(new CookedDataRun(run, vcn, lcn, attributeExtent));
vcn += run.RunLength;
lcn += run.RunOffset;
}
}
public void MakeSparse(int index)
{
if (index < _firstDirty)
{
_firstDirty = index;
}
if (index > _lastDirty)
{
_lastDirty = index;
}
long prevLcn = index == 0 ? 0 : _runs[index - 1].StartLcn;
CookedDataRun run = _runs[index];
if (run.IsSparse)
{
throw new ArgumentException("Run is already sparse", "index");
}
_runs[index] = new CookedDataRun(new DataRun(0, run.Length, true), run.StartVcn, prevLcn, run.AttributeExtent);
run.AttributeExtent.ReplaceRun(run.DataRun, _runs[index].DataRun);
for (int i = index + 1; i < _runs.Count; ++i)
{
if (!_runs[i].IsSparse)
{
_runs[i].DataRun.RunOffset += run.StartLcn - prevLcn;
break;
}
}
}
public void MakeNonSparse(int index, IEnumerable<DataRun> rawRuns)
{
if (index < _firstDirty)
{
_firstDirty = index;
}
if (index > _lastDirty)
{
_lastDirty = index;
}
long prevLcn = index == 0 ? 0 : _runs[index - 1].StartLcn;
CookedDataRun run = _runs[index];
if (!run.IsSparse)
{
throw new ArgumentException("Run is already non-sparse", "index");
}
_runs.RemoveAt(index);
int insertIdx = run.AttributeExtent.RemoveRun(run.DataRun);
CookedDataRun lastNewRun = null;
long lcn = prevLcn;
long vcn = run.StartVcn;
foreach (var rawRun in rawRuns)
{
CookedDataRun newRun = new CookedDataRun(rawRun, vcn, lcn, run.AttributeExtent);
_runs.Insert(index, newRun);
run.AttributeExtent.InsertRun(insertIdx, rawRun);
vcn += rawRun.RunLength;
lcn += rawRun.RunOffset;
lastNewRun = newRun;
insertIdx++;
index++;
}
for (int i = index; i < _runs.Count; ++i)
{
if (_runs[i].IsSparse)
{
_runs[i].StartLcn = lastNewRun.StartLcn;
}
else
{
_runs[i].DataRun.RunOffset = _runs[i].StartLcn - lastNewRun.StartLcn;
break;
}
}
}
public void SplitRun(int runIdx, long vcn)
{
if (runIdx < _firstDirty)
{
_firstDirty = runIdx;
}
if (runIdx > _lastDirty)
{
_lastDirty = runIdx;
}
CookedDataRun run = _runs[runIdx];
if (run.StartVcn >= vcn || run.StartVcn + run.Length <= vcn)
{
throw new ArgumentException("Attempt to split run outside of it's range", "vcn");
}
long distance = vcn - run.StartVcn;
long offset = run.IsSparse ? 0 : distance;
CookedDataRun newRun = new CookedDataRun(new DataRun(offset, run.Length - distance, run.IsSparse), vcn, run.StartLcn, run.AttributeExtent);
run.Length = distance;
_runs.Insert(runIdx + 1, newRun);
run.AttributeExtent.InsertRun(run.DataRun, newRun.DataRun);
for (int i = runIdx + 2; i < _runs.Count; ++i)
{
if (_runs[i].IsSparse)
{
_runs[i].StartLcn += offset;
}
else
{
_runs[i].DataRun.RunOffset -= offset;
break;
}
}
}
/// <summary>
/// Truncates the set of data runs.
/// </summary>
/// <param name="index">The first run to be truncated.</param>
public void TruncateAt(int index)
{
while (index < _runs.Count)
{
_runs[index].AttributeExtent.RemoveRun(_runs[index].DataRun);
_runs.RemoveAt(index);
}
}
internal void CollapseRuns()
{
int i = _firstDirty > 1 ? _firstDirty - 1 : 0;
while (i < _runs.Count - 1 && i <= _lastDirty + 1)
{
if (_runs[i].IsSparse && _runs[i + 1].IsSparse)
{
_runs[i].Length += _runs[i + 1].Length;
_runs[i + 1].AttributeExtent.RemoveRun(_runs[i + 1].DataRun);
_runs.RemoveAt(i + 1);
}
else if (!_runs[i].IsSparse && !_runs[i].IsSparse && _runs[i].StartLcn + _runs[i].Length == _runs[i + 1].StartLcn)
{
_runs[i].Length += _runs[i + 1].Length;
_runs[i + 1].AttributeExtent.RemoveRun(_runs[i + 1].DataRun);
_runs.RemoveAt(i + 1);
for (int j = i + 1; j < _runs.Count; ++j)
{
if (_runs[j].IsSparse)
{
_runs[j].StartLcn = _runs[i].StartLcn;
}
else
{
_runs[j].DataRun.RunOffset = _runs[j].StartLcn - _runs[i].StartLcn;
break;
}
}
}
else
{
++i;
}
}
_firstDirty = int.MaxValue;
_lastDirty = 0;
}
}
}
+173
View File
@@ -0,0 +1,173 @@
//
// 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.Globalization;
using System.IO;
internal class DataRun
{
private long _runLength;
private long _runOffset;
private bool _isSparse;
public DataRun()
{
}
public DataRun(long offset, long length, bool isSparse)
{
_runOffset = offset;
_runLength = length;
_isSparse = isSparse;
}
public long RunLength
{
get { return _runLength; }
set { _runLength = value; }
}
public long RunOffset
{
get { return _runOffset; }
set { _runOffset = value; }
}
public bool IsSparse
{
get { return _isSparse; }
}
internal int Size
{
get
{
int runLengthSize = VarLongSize(_runLength);
int runOffsetSize = VarLongSize(_runOffset);
return 1 + runLengthSize + runOffsetSize;
}
}
public int Read(byte[] buffer, int offset)
{
int runOffsetSize = (buffer[offset] >> 4) & 0x0F;
int runLengthSize = buffer[offset] & 0x0F;
_runLength = ReadVarLong(buffer, offset + 1, runLengthSize);
_runOffset = ReadVarLong(buffer, offset + 1 + runLengthSize, runOffsetSize);
_isSparse = runOffsetSize == 0;
return 1 + runLengthSize + runOffsetSize;
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0:+##;-##;0}[+{1}]", _runOffset, _runLength);
}
internal int Write(byte[] buffer, int offset)
{
int runLengthSize = WriteVarLong(buffer, offset + 1, _runLength);
int runOffsetSize = _isSparse ? 0 : WriteVarLong(buffer, offset + 1 + runLengthSize, _runOffset);
buffer[offset] = (byte)((runLengthSize & 0x0F) | ((runOffsetSize << 4) & 0xF0));
return 1 + runLengthSize + runOffsetSize;
}
private static long ReadVarLong(byte[] buffer, int offset, int size)
{
ulong val = 0;
bool signExtend = false;
for (int i = 0; i < size; ++i)
{
byte b = buffer[offset + i];
val = val | (((ulong)b) << (i * 8));
signExtend = (b & 0x80) != 0;
}
if (signExtend)
{
for (int i = size; i < 8; ++i)
{
val = val | (((ulong)0xFF) << (i * 8));
}
}
return (long)val;
}
private static int WriteVarLong(byte[] buffer, int offset, long val)
{
bool isPositive = val >= 0;
int pos = 0;
do
{
buffer[offset + pos] = (byte)(val & 0xFF);
val >>= 8;
pos++;
}
while (val != 0 && val != -1);
// Avoid appearing to have a negative number that is actually positive,
// record an extra empty byte if needed.
if (isPositive && (buffer[offset + pos - 1] & 0x80) != 0)
{
buffer[offset + pos] = 0;
pos++;
}
else if (!isPositive && (buffer[offset + pos - 1] & 0x80) != 0x80)
{
buffer[offset + pos] = 0xFF;
pos++;
}
return pos;
}
private static int VarLongSize(long val)
{
bool isPositive = val >= 0;
bool lastByteHighBitSet = false;
int len = 0;
do
{
lastByteHighBitSet = (val & 0x80) != 0;
val >>= 8;
len++;
}
while (val != 0 && val != -1);
if ((isPositive && lastByteHighBitSet) || (!isPositive && !lastByteHighBitSet))
{
len++;
}
return len;
}
}
}
+291
View File
@@ -0,0 +1,291 @@
//
// 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;
using System.Text;
using DirectoryIndexEntry = System.Collections.Generic.KeyValuePair<DiscUtils.Ntfs.FileNameRecord, DiscUtils.Ntfs.FileRecordReference>;
internal class Directory : File
{
private IndexView<FileNameRecord, FileRecordReference> _index;
public Directory(INtfsContext context, FileRecord baseRecord)
: base(context, baseRecord)
{
}
public bool IsEmpty
{
get { return Index.Count == 0; }
}
private IndexView<FileNameRecord, FileRecordReference> Index
{
get
{
if (_index == null && StreamExists(AttributeType.IndexRoot, "$I30"))
{
_index = new IndexView<FileNameRecord, FileRecordReference>(GetIndex("$I30"));
}
return _index;
}
}
public IEnumerable<DirectoryEntry> GetAllEntries(bool filter)
{
IEnumerable<DirectoryIndexEntry> entries = filter ? FilterEntries(Index.Entries) : Index.Entries;
foreach (var entry in entries)
{
yield return new DirectoryEntry(this, entry.Value, entry.Key);
}
}
public void UpdateEntry(DirectoryEntry entry)
{
Index[entry.Details] = entry.Reference;
UpdateRecordInMft();
}
public override void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "DIRECTORY (" + base.ToString() + ")");
writer.WriteLine(indent + " File Number: " + IndexInMft);
if (Index != null)
{
foreach (var entry in Index.Entries)
{
writer.WriteLine(indent + " DIRECTORY ENTRY (" + entry.Key.FileName + ")");
writer.WriteLine(indent + " MFT Ref: " + entry.Value);
entry.Key.Dump(writer, indent + " ");
}
}
}
public override string ToString()
{
return base.ToString() + @"\";
}
internal static new Directory CreateNew(INtfsContext context, FileAttributeFlags parentDirFlags)
{
Directory dir = (Directory)context.AllocateFile(FileRecordFlags.IsDirectory);
StandardInformation.InitializeNewFile(
dir,
FileAttributeFlags.Archive | (parentDirFlags & FileAttributeFlags.Compressed));
// Create the index root attribute by instantiating a new index
dir.CreateIndex("$I30", AttributeType.FileName, AttributeCollationRule.Filename);
dir.UpdateRecordInMft();
return dir;
}
internal DirectoryEntry GetEntryByName(string name)
{
string searchName = name;
int streamSepPos = name.IndexOf(':');
if (streamSepPos >= 0)
{
searchName = name.Substring(0, streamSepPos);
}
DirectoryIndexEntry entry = Index.FindFirst(new FileNameQuery(searchName, _context.UpperCase));
if (entry.Key != null)
{
return new DirectoryEntry(this, entry.Value, entry.Key);
}
else
{
return null;
}
}
internal DirectoryEntry AddEntry(File file, string name, FileNameNamespace nameNamespace)
{
if (name.Length > 255)
{
throw new IOException("Invalid file name, more than 255 characters: " + name);
}
else if (name.IndexOfAny(new char[] { '\0', '/' }) != -1)
{
throw new IOException(@"Invalid file name, contains '\0' or '/': " + name);
}
FileNameRecord newNameRecord = file.GetFileNameRecord(null, true);
newNameRecord.FileNameNamespace = nameNamespace;
newNameRecord.FileName = name;
newNameRecord.ParentDirectory = MftReference;
NtfsStream nameStream = file.CreateStream(AttributeType.FileName, null);
nameStream.SetContent(newNameRecord);
file.HardLinkCount++;
file.UpdateRecordInMft();
Index[newNameRecord] = file.MftReference;
Modified();
UpdateRecordInMft();
return new DirectoryEntry(this, file.MftReference, newNameRecord);
}
internal void RemoveEntry(DirectoryEntry dirEntry)
{
File file = _context.GetFileByRef(dirEntry.Reference);
FileNameRecord nameRecord = dirEntry.Details;
Index.Remove(dirEntry.Details);
foreach (NtfsStream stream in file.GetStreams(AttributeType.FileName, null))
{
FileNameRecord streamName = stream.GetContent<FileNameRecord>();
if (nameRecord.Equals(streamName))
{
file.RemoveStream(stream);
break;
}
}
file.HardLinkCount--;
file.UpdateRecordInMft();
Modified();
UpdateRecordInMft();
}
internal string CreateShortName(string name)
{
string baseName = string.Empty;
string ext = string.Empty;
int lastPeriod = name.LastIndexOf('.');
int i = 0;
while (baseName.Length < 6 && i < name.Length && i != lastPeriod)
{
char upperChar = Char.ToUpperInvariant(name[i]);
if (Utilities.Is8Dot3Char(upperChar))
{
baseName += upperChar;
}
++i;
}
if (lastPeriod >= 0)
{
i = lastPeriod + 1;
while (ext.Length < 3 && i < name.Length)
{
char upperChar = Char.ToUpperInvariant(name[i]);
if (Utilities.Is8Dot3Char(upperChar))
{
ext += upperChar;
}
++i;
}
}
i = 1;
string candidate;
do
{
string suffix = string.Format(CultureInfo.InvariantCulture, "~{0}", i);
candidate = baseName.Substring(0, Math.Min(8 - suffix.Length, baseName.Length)) + suffix + (ext.Length > 0 ? "." + ext : string.Empty);
i++;
}
while (GetEntryByName(candidate) != null);
return candidate;
}
private List<DirectoryIndexEntry> FilterEntries(IEnumerable<DirectoryIndexEntry> entriesIter)
{
List<DirectoryIndexEntry> entries = new List<DirectoryIndexEntry>(entriesIter);
// Weed out short-name entries for files and any hidden / system / metadata files.
int i = 0;
while (i < entries.Count)
{
DirectoryIndexEntry entry = entries[i];
if (((entry.Key.Flags & FileAttributeFlags.Hidden) != 0) && _context.Options.HideHiddenFiles)
{
entries.RemoveAt(i);
}
else if (((entry.Key.Flags & FileAttributeFlags.System) != 0) && _context.Options.HideSystemFiles)
{
entries.RemoveAt(i);
}
else if (entry.Value.MftIndex < 24 && _context.Options.HideMetafiles)
{
entries.RemoveAt(i);
}
else if (entry.Key.FileNameNamespace == FileNameNamespace.Dos && _context.Options.HideDosFileNames)
{
entries.RemoveAt(i);
}
else
{
++i;
}
}
return entries;
}
private sealed class FileNameQuery : IComparable<byte[]>
{
private byte[] _query;
private UpperCase _upperCase;
public FileNameQuery(string query, UpperCase upperCase)
{
_query = Encoding.Unicode.GetBytes(query);
_upperCase = upperCase;
}
public int CompareTo(byte[] buffer)
{
// Note: this is internal knowledge of FileNameRecord structure - but for performance
// reasons, we don't want to decode the entire structure. In fact can avoid the string
// conversion as well.
byte fnLen = buffer[0x40];
return _upperCase.Compare(_query, 0, _query.Length, buffer, 0x42, fnLen * 2);
}
}
}
}
+75
View File
@@ -0,0 +1,75 @@
//
// 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
{
internal class DirectoryEntry
{
private Directory _directory;
private FileRecordReference _fileReference;
private FileNameRecord _fileDetails;
public DirectoryEntry(Directory directory, FileRecordReference fileReference, FileNameRecord fileDetails)
{
_directory = directory;
_fileReference = fileReference;
_fileDetails = fileDetails;
}
public FileRecordReference Reference
{
get { return _fileReference; }
}
public FileNameRecord Details
{
get { return _fileDetails; }
}
public bool IsDirectory
{
get { return (_fileDetails.Flags & FileAttributeFlags.Directory) != 0; }
}
public string SearchName
{
get
{
string fileName = _fileDetails.FileName;
if (fileName.IndexOf('.') == -1)
{
return fileName + ".";
}
else
{
return fileName;
}
}
}
internal void UpdateFrom(File file)
{
file.FreshenFileName(_fileDetails, true);
_directory.UpdateEntry(this);
}
}
}
File diff suppressed because it is too large Load Diff
+210
View File
@@ -0,0 +1,210 @@
//
// 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.IO;
using System.Text;
[Flags]
internal enum FileAttributeFlags : uint
{
None = 0x00000000,
ReadOnly = 0x00000001,
Hidden = 0x00000002,
System = 0x00000004,
Archive = 0x00000020,
Device = 0x00000040,
Normal = 0x00000080,
Temporary = 0x00000100,
Sparse = 0x00000200,
ReparsePoint = 0x00000400,
Compressed = 0x00000800,
Offline = 0x00001000,
NotIndexed = 0x00002000,
Encrypted = 0x00004000,
Directory = 0x10000000,
IndexView = 0x20000000
}
internal enum FileNameNamespace : byte
{
Posix = 0,
Win32 = 1,
Dos = 2,
Win32AndDos = 3
}
internal class FileNameRecord : IByteArraySerializable, IDiagnosticTraceable, IEquatable<FileNameRecord>
{
public FileRecordReference ParentDirectory;
public DateTime CreationTime;
public DateTime ModificationTime;
public DateTime MftChangedTime;
public DateTime LastAccessTime;
public ulong AllocatedSize;
public ulong RealSize;
public FileAttributeFlags Flags;
public uint EASizeOrReparsePointTag;
public FileNameNamespace FileNameNamespace;
public string FileName;
public FileNameRecord()
{
}
public FileNameRecord(FileNameRecord toCopy)
{
ParentDirectory = toCopy.ParentDirectory;
CreationTime = toCopy.CreationTime;
ModificationTime = toCopy.ModificationTime;
MftChangedTime = toCopy.MftChangedTime;
LastAccessTime = toCopy.LastAccessTime;
AllocatedSize = toCopy.AllocatedSize;
RealSize = toCopy.RealSize;
Flags = toCopy.Flags;
EASizeOrReparsePointTag = toCopy.EASizeOrReparsePointTag;
FileNameNamespace = toCopy.FileNameNamespace;
FileName = toCopy.FileName;
}
public FileAttributes FileAttributes
{
get { return ConvertFlags(Flags); }
}
public int Size
{
get
{
return 0x42 + (FileName.Length * 2);
}
}
public override string ToString()
{
return FileName;
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "FILE NAME RECORD");
writer.WriteLine(indent + " Parent Directory: " + ParentDirectory);
writer.WriteLine(indent + " Creation Time: " + CreationTime);
writer.WriteLine(indent + " Modification Time: " + ModificationTime);
writer.WriteLine(indent + " MFT Changed Time: " + MftChangedTime);
writer.WriteLine(indent + " Last Access Time: " + LastAccessTime);
writer.WriteLine(indent + " Allocated Size: " + AllocatedSize);
writer.WriteLine(indent + " Real Size: " + RealSize);
writer.WriteLine(indent + " Flags: " + Flags);
if ((Flags & FileAttributeFlags.ReparsePoint) != 0)
{
writer.WriteLine(indent + " Reparse Point Tag: " + EASizeOrReparsePointTag);
}
else
{
writer.WriteLine(indent + " Ext Attr Size: " + (EASizeOrReparsePointTag & 0xFFFF));
}
writer.WriteLine(indent + " Namespace: " + FileNameNamespace);
writer.WriteLine(indent + " File Name: " + FileName);
}
public int ReadFrom(byte[] buffer, int offset)
{
ParentDirectory = new FileRecordReference(Utilities.ToUInt64LittleEndian(buffer, offset + 0x00));
CreationTime = ReadDateTime(buffer, offset + 0x08);
ModificationTime = ReadDateTime(buffer, offset + 0x10);
MftChangedTime = ReadDateTime(buffer, offset + 0x18);
LastAccessTime = ReadDateTime(buffer, offset + 0x20);
AllocatedSize = Utilities.ToUInt64LittleEndian(buffer, offset + 0x28);
RealSize = Utilities.ToUInt64LittleEndian(buffer, offset + 0x30);
Flags = (FileAttributeFlags)Utilities.ToUInt32LittleEndian(buffer, offset + 0x38);
EASizeOrReparsePointTag = Utilities.ToUInt32LittleEndian(buffer, offset + 0x3C);
byte fnLen = buffer[offset + 0x40];
FileNameNamespace = (FileNameNamespace)buffer[offset + 0x41];
FileName = Encoding.Unicode.GetString(buffer, offset + 0x42, fnLen * 2);
return 0x42 + (fnLen * 2);
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian((ulong)ParentDirectory.Value, buffer, offset + 0x00);
Utilities.WriteBytesLittleEndian((ulong)CreationTime.ToFileTimeUtc(), buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian((ulong)ModificationTime.ToFileTimeUtc(), buffer, offset + 0x10);
Utilities.WriteBytesLittleEndian((ulong)MftChangedTime.ToFileTimeUtc(), buffer, offset + 0x18);
Utilities.WriteBytesLittleEndian((ulong)LastAccessTime.ToFileTimeUtc(), buffer, offset + 0x20);
Utilities.WriteBytesLittleEndian(AllocatedSize, buffer, offset + 0x28);
Utilities.WriteBytesLittleEndian(RealSize, buffer, offset + 0x30);
Utilities.WriteBytesLittleEndian((uint)Flags, buffer, offset + 0x38);
Utilities.WriteBytesLittleEndian(EASizeOrReparsePointTag, buffer, offset + 0x3C);
buffer[offset + 0x40] = (byte)FileName.Length;
buffer[offset + 0x41] = (byte)FileNameNamespace;
Encoding.Unicode.GetBytes(FileName, 0, FileName.Length, buffer, offset + 0x42);
}
public bool Equals(FileNameRecord other)
{
if (other == null)
{
return false;
}
return ParentDirectory == other.ParentDirectory
&& FileNameNamespace == other.FileNameNamespace
&& FileName == other.FileName;
}
internal static FileAttributeFlags SetAttributes(FileAttributes attrs, FileAttributeFlags flags)
{
FileAttributes attrMask = ((FileAttributes)0xFFFF) & ~FileAttributes.Directory;
return (FileAttributeFlags)(((uint)flags & 0xFFFF0000) | (uint)(attrs & attrMask));
}
internal static FileAttributes ConvertFlags(FileAttributeFlags flags)
{
FileAttributes result = (FileAttributes)(((uint)flags) & 0xFFFF);
if ((flags & FileAttributeFlags.Directory) != 0)
{
result |= FileAttributes.Directory;
}
return result;
}
private static DateTime ReadDateTime(byte[] buffer, int offset)
{
try
{
return DateTime.FromFileTimeUtc(Utilities.ToInt64LittleEndian(buffer, offset));
}
catch (ArgumentException)
{
return DateTime.MinValue;
}
}
}
}
+468
View File
@@ -0,0 +1,468 @@
//
// 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.Collections.ObjectModel;
using System.IO;
[Flags]
internal enum FileRecordFlags : ushort
{
None = 0x0000,
InUse = 0x0001,
IsDirectory = 0x0002,
IsMetaFile = 0x0004,
HasViewIndex = 0x0008
}
internal class FileRecord : FixupRecordBase
{
private ulong _logFileSequenceNumber;
private ushort _sequenceNumber;
private ushort _hardLinkCount;
private ushort _firstAttributeOffset;
private FileRecordFlags _flags;
private uint _recordRealSize;
private uint _recordAllocatedSize;
private FileRecordReference _baseFile;
private ushort _nextAttributeId;
private uint _index; // Self-reference (on XP+)
private List<AttributeRecord> _attributes;
private bool _haveIndex;
private uint _loadedIndex;
public FileRecord(int sectorSize)
: base("FILE", sectorSize)
{
}
public FileRecord(int sectorSize, int recordLength, uint index)
: base("FILE", sectorSize, recordLength)
{
ReInitialize(sectorSize, recordLength, index);
}
public uint MasterFileTableIndex
{
get { return _haveIndex ? _index : _loadedIndex; }
}
public uint LoadedIndex
{
get { return _loadedIndex; }
set { _loadedIndex = value; }
}
public ulong LogFileSequenceNumber
{
get { return _logFileSequenceNumber; }
}
public ushort SequenceNumber
{
get { return _sequenceNumber; }
set { _sequenceNumber = value; }
}
public ushort HardLinkCount
{
get { return _hardLinkCount; }
set { _hardLinkCount = value; }
}
public uint AllocatedSize
{
get { return _recordAllocatedSize; }
}
public uint RealSize
{
get { return _recordRealSize; }
}
public FileRecordReference BaseFile
{
get { return _baseFile; }
set { _baseFile = value; }
}
public FileRecordFlags Flags
{
get { return _flags; }
set { _flags = value; }
}
public List<AttributeRecord> Attributes
{
get { return _attributes; }
}
public AttributeRecord FirstAttribute
{
get { return _attributes.Count > 0 ? _attributes[0] : null; }
}
public FileRecordReference Reference
{
get { return new FileRecordReference(MasterFileTableIndex, SequenceNumber); }
}
public ushort NextAttributeId
{
get { return _nextAttributeId; }
}
public bool IsMftRecord
{
get { return MasterFileTableIndex == MasterFileTable.MftIndex || (_baseFile.MftIndex == MasterFileTable.MftIndex && _baseFile.SequenceNumber != 0); }
}
public static FileAttributeFlags ConvertFlags(FileRecordFlags source)
{
FileAttributeFlags result = FileAttributeFlags.None;
if ((source & FileRecordFlags.IsDirectory) != 0)
{
result |= FileAttributeFlags.Directory;
}
if ((source & FileRecordFlags.HasViewIndex) != 0)
{
result |= FileAttributeFlags.IndexView;
}
if ((source & FileRecordFlags.IsMetaFile) != 0)
{
result |= FileAttributeFlags.Hidden | FileAttributeFlags.System;
}
return result;
}
public void ReInitialize(int sectorSize, int recordLength, uint index)
{
Initialize("FILE", sectorSize, recordLength);
_sequenceNumber++;
_flags = FileRecordFlags.None;
_recordAllocatedSize = (uint)recordLength;
_nextAttributeId = 0;
_index = index;
_hardLinkCount = 0;
_baseFile = new FileRecordReference(0);
_attributes = new List<AttributeRecord>();
_haveIndex = true;
}
/// <summary>
/// Gets an attribute by it's id.
/// </summary>
/// <param name="id">The attribute's id.</param>
/// <returns>The attribute, or <c>null</c>.</returns>
public AttributeRecord GetAttribute(ushort id)
{
foreach (AttributeRecord attrRec in _attributes)
{
if (attrRec.AttributeId == id)
{
return attrRec;
}
}
return null;
}
/// <summary>
/// Gets an unnamed attribute.
/// </summary>
/// <param name="type">The attribute type.</param>
/// <returns>The attribute, or <c>null</c>.</returns>
public AttributeRecord GetAttribute(AttributeType type)
{
return GetAttribute(type, null);
}
/// <summary>
/// Gets an named attribute.
/// </summary>
/// <param name="type">The attribute type.</param>
/// <param name="name">The name of the attribute.</param>
/// <returns>The attribute, or <c>null</c>.</returns>
public AttributeRecord GetAttribute(AttributeType type, string name)
{
foreach (AttributeRecord attrRec in _attributes)
{
if (attrRec.AttributeType == type && attrRec.Name == name)
{
return attrRec;
}
}
return null;
}
public override string ToString()
{
foreach (AttributeRecord attr in _attributes)
{
if (attr.AttributeType == AttributeType.FileName)
{
StructuredNtfsAttribute<FileNameRecord> fnAttr = (StructuredNtfsAttribute<FileNameRecord>)NtfsAttribute.FromRecord(null, new FileRecordReference(0), attr);
return fnAttr.Content.FileName;
}
}
return "No Name";
}
/// <summary>
/// Creates a new attribute.
/// </summary>
/// <param name="type">The type of the new attribute.</param>
/// <param name="name">The name of the new attribute.</param>
/// <param name="indexed">Whether the attribute is marked as indexed.</param>
/// <param name="flags">Flags for the new attribute.</param>
/// <returns>The id of the new attribute.</returns>
public ushort CreateAttribute(AttributeType type, string name, bool indexed, AttributeFlags flags)
{
ushort id = _nextAttributeId++;
_attributes.Add(
new ResidentAttributeRecord(
type,
name,
id,
indexed,
flags));
_attributes.Sort();
return id;
}
/// <summary>
/// Creates a new non-resident attribute.
/// </summary>
/// <param name="type">The type of the new attribute.</param>
/// <param name="name">The name of the new attribute.</param>
/// <param name="flags">Flags for the new attribute.</param>
/// <returns>The id of the new attribute.</returns>
public ushort CreateNonResidentAttribute(AttributeType type, string name, AttributeFlags flags)
{
ushort id = _nextAttributeId++;
_attributes.Add(
new NonResidentAttributeRecord(
type,
name,
id,
flags,
0,
new List<DataRun>()));
_attributes.Sort();
return id;
}
/// <summary>
/// Creates a new attribute.
/// </summary>
/// <param name="type">The type of the new attribute.</param>
/// <param name="name">The name of the new attribute.</param>
/// <param name="flags">Flags for the new attribute.</param>
/// <param name="firstCluster">The first cluster to assign to the attribute.</param>
/// <param name="numClusters">The number of sequential clusters to assign to the attribute.</param>
/// <param name="bytesPerCluster">The number of bytes in each cluster.</param>
/// <returns>The id of the new attribute.</returns>
public ushort CreateNonResidentAttribute(AttributeType type, string name, AttributeFlags flags, long firstCluster, ulong numClusters, uint bytesPerCluster)
{
ushort id = _nextAttributeId++;
_attributes.Add(
new NonResidentAttributeRecord(
type,
name,
id,
flags,
firstCluster,
numClusters,
bytesPerCluster));
_attributes.Sort();
return id;
}
/// <summary>
/// Adds an existing attribute.
/// </summary>
/// <param name="attrRec">The attribute to add.</param>
/// <returns>The new Id of the attribute.</returns>
/// <remarks>This method is used to move an attribute between different MFT records.</remarks>
public ushort AddAttribute(AttributeRecord attrRec)
{
attrRec.AttributeId = _nextAttributeId++;
_attributes.Add(attrRec);
_attributes.Sort();
return attrRec.AttributeId;
}
/// <summary>
/// Removes an attribute by it's id.
/// </summary>
/// <param name="id">The attribute's id.</param>
public void RemoveAttribute(ushort id)
{
for (int i = 0; i < _attributes.Count; ++i)
{
if (_attributes[i].AttributeId == id)
{
_attributes.RemoveAt(i);
break;
}
}
}
public void Reset()
{
_attributes.Clear();
_flags = FileRecordFlags.None;
_hardLinkCount = 0;
_nextAttributeId = 0;
_recordRealSize = 0;
}
internal long GetAttributeOffset(ushort id)
{
int firstAttrPos = (ushort)Utilities.RoundUp((_haveIndex ? 0x30 : 0x2A) + UpdateSequenceSize, 8);
int offset = firstAttrPos;
foreach (var attr in _attributes)
{
if (attr.AttributeId == id)
{
return offset;
}
offset += attr.Size;
}
return -1;
}
internal void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "FILE RECORD (" + ToString() + ")");
writer.WriteLine(indent + " Magic: " + Magic);
writer.WriteLine(indent + " Update Seq Offset: " + UpdateSequenceOffset);
writer.WriteLine(indent + " Update Seq Count: " + UpdateSequenceCount);
writer.WriteLine(indent + " Update Seq Number: " + UpdateSequenceNumber);
writer.WriteLine(indent + " Log File Seq Num: " + _logFileSequenceNumber);
writer.WriteLine(indent + " Sequence Number: " + _sequenceNumber);
writer.WriteLine(indent + " Hard Link Count: " + _hardLinkCount);
writer.WriteLine(indent + " Flags: " + _flags);
writer.WriteLine(indent + " Record Real Size: " + _recordRealSize);
writer.WriteLine(indent + " Record Alloc Size: " + _recordAllocatedSize);
writer.WriteLine(indent + " Base File: " + _baseFile);
writer.WriteLine(indent + " Next Attribute Id: " + _nextAttributeId);
writer.WriteLine(indent + " Attribute Count: " + _attributes.Count);
writer.WriteLine(indent + " Index (Self Ref): " + _index);
}
protected override void Read(byte[] buffer, int offset)
{
_logFileSequenceNumber = Utilities.ToUInt64LittleEndian(buffer, offset + 0x08);
_sequenceNumber = Utilities.ToUInt16LittleEndian(buffer, offset + 0x10);
_hardLinkCount = Utilities.ToUInt16LittleEndian(buffer, offset + 0x12);
_firstAttributeOffset = Utilities.ToUInt16LittleEndian(buffer, offset + 0x14);
_flags = (FileRecordFlags)Utilities.ToUInt16LittleEndian(buffer, offset + 0x16);
_recordRealSize = Utilities.ToUInt32LittleEndian(buffer, offset + 0x18);
_recordAllocatedSize = Utilities.ToUInt32LittleEndian(buffer, offset + 0x1C);
_baseFile = new FileRecordReference(Utilities.ToUInt64LittleEndian(buffer, offset + 0x20));
_nextAttributeId = Utilities.ToUInt16LittleEndian(buffer, offset + 0x28);
if (UpdateSequenceOffset >= 0x30)
{
_index = Utilities.ToUInt32LittleEndian(buffer, offset + 0x2C);
_haveIndex = true;
}
_attributes = new List<AttributeRecord>();
int focus = _firstAttributeOffset;
while (true)
{
int length;
AttributeRecord attr = AttributeRecord.FromBytes(buffer, focus, out length);
if (attr == null)
{
break;
}
_attributes.Add(attr);
focus += (int)length;
}
}
protected override ushort Write(byte[] buffer, int offset)
{
ushort headerEnd = (ushort)(_haveIndex ? 0x30 : 0x2A);
_firstAttributeOffset = (ushort)Utilities.RoundUp(headerEnd + UpdateSequenceSize, 0x08);
_recordRealSize = (uint)CalcSize();
Utilities.WriteBytesLittleEndian(_logFileSequenceNumber, buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian(_sequenceNumber, buffer, offset + 0x10);
Utilities.WriteBytesLittleEndian(_hardLinkCount, buffer, offset + 0x12);
Utilities.WriteBytesLittleEndian(_firstAttributeOffset, buffer, offset + 0x14);
Utilities.WriteBytesLittleEndian((ushort)_flags, buffer, offset + 0x16);
Utilities.WriteBytesLittleEndian(_recordRealSize, buffer, offset + 0x18);
Utilities.WriteBytesLittleEndian(_recordAllocatedSize, buffer, offset + 0x1C);
Utilities.WriteBytesLittleEndian(_baseFile.Value, buffer, offset + 0x20);
Utilities.WriteBytesLittleEndian(_nextAttributeId, buffer, offset + 0x28);
if (_haveIndex)
{
Utilities.WriteBytesLittleEndian((ushort)0, buffer, offset + 0x2A); // Alignment field
Utilities.WriteBytesLittleEndian(_index, buffer, offset + 0x2C);
}
int pos = _firstAttributeOffset;
foreach (var attr in _attributes)
{
pos += attr.Write(buffer, offset + pos);
}
Utilities.WriteBytesLittleEndian(uint.MaxValue, buffer, offset + pos);
return headerEnd;
}
protected override int CalcSize()
{
int firstAttrPos = (ushort)Utilities.RoundUp((_haveIndex ? 0x30 : 0x2A) + UpdateSequenceSize, 8);
int size = firstAttrPos;
foreach (var attr in _attributes)
{
size += attr.Size;
}
return Utilities.RoundUp(size + 4, 8); // 0xFFFFFFFF terminator on attributes
}
}
}
+123
View File
@@ -0,0 +1,123 @@
//
// 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;
internal struct FileRecordReference : IByteArraySerializable, IComparable<FileRecordReference>
{
private ulong _val;
public FileRecordReference(ulong val)
{
_val = val;
}
public FileRecordReference(long mftIndex, ushort sequenceNumber)
{
_val = (ulong)(mftIndex & 0x0000FFFFFFFFFFFFL) | ((ulong)((ulong)sequenceNumber << 48) & 0xFFFF000000000000L);
}
public ulong Value
{
get { return _val; }
}
public long MftIndex
{
get { return (long)(_val & 0x0000FFFFFFFFFFFFL); }
}
public ushort SequenceNumber
{
get { return (ushort)((_val >> 48) & 0xFFFF); }
}
public int Size
{
get { return 8; }
}
public bool IsNull
{
get { return SequenceNumber == 0; }
}
public static bool operator ==(FileRecordReference a, FileRecordReference b)
{
return a._val == b._val;
}
public static bool operator !=(FileRecordReference a, FileRecordReference b)
{
return a._val != b._val;
}
public int ReadFrom(byte[] buffer, int offset)
{
_val = Utilities.ToUInt64LittleEndian(buffer, offset);
return 8;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(_val, buffer, offset);
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is FileRecordReference))
{
return false;
}
return _val == ((FileRecordReference)obj)._val;
}
public override int GetHashCode()
{
return _val.GetHashCode();
}
public int CompareTo(FileRecordReference other)
{
if (_val < other._val)
{
return -1;
}
else if (_val > other._val)
{
return 1;
}
else
{
return 0;
}
}
public override string ToString()
{
return "MFT:" + MftIndex + " (ver: " + SequenceNumber + ")";
}
}
}
+46
View File
@@ -0,0 +1,46 @@
//
// 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.IO;
using DiscUtils.Vfs;
[VfsFileSystemFactory]
internal class FileSystemFactory : VfsFileSystemFactory
{
public override DiscUtils.FileSystemInfo[] Detect(Stream stream, VolumeInfo volume)
{
if (NtfsFileSystem.Detect(stream))
{
return new DiscUtils.FileSystemInfo[] { new VfsFileSystemInfo("NTFS", "Microsoft NTFS", Open) };
}
return new DiscUtils.FileSystemInfo[0];
}
private DiscFileSystem Open(Stream stream, VolumeInfo volumeInfo, FileSystemParameters parameters)
{
return new NtfsFileSystem(stream);
}
}
}
+190
View File
@@ -0,0 +1,190 @@
//
// 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.IO;
internal abstract class FixupRecordBase
{
private int _sectorSize;
private string _magic;
private ushort _updateSequenceOffset;
private ushort _updateSequenceCount;
private ushort _updateSequenceNumber;
private ushort[] _updateSequenceArray;
public FixupRecordBase(string magic, int sectorSize)
{
_magic = magic;
_sectorSize = sectorSize;
}
public FixupRecordBase(string magic, int sectorSize, int recordLength)
{
Initialize(magic, sectorSize, recordLength);
}
public string Magic
{
get { return _magic; }
}
public ushort UpdateSequenceOffset
{
get { return _updateSequenceOffset; }
}
public ushort UpdateSequenceCount
{
get { return _updateSequenceCount; }
}
public ushort UpdateSequenceNumber
{
get { return _updateSequenceNumber; }
}
public int UpdateSequenceSize
{
get { return _updateSequenceCount * 2; }
}
public int Size
{
get
{
return CalcSize();
}
}
public void FromBytes(byte[] buffer, int offset)
{
FromBytes(buffer, offset, false);
}
public void FromBytes(byte[] buffer, int offset, bool ignoreMagic)
{
string diskMagic = Utilities.BytesToString(buffer, offset + 0x00, 4);
if (_magic == null)
{
_magic = diskMagic;
}
else
{
if (diskMagic != _magic && ignoreMagic)
{
return;
}
if (diskMagic != _magic)
{
throw new IOException("Corrupt record");
}
}
_updateSequenceOffset = Utilities.ToUInt16LittleEndian(buffer, offset + 0x04);
_updateSequenceCount = Utilities.ToUInt16LittleEndian(buffer, offset + 0x06);
_updateSequenceNumber = Utilities.ToUInt16LittleEndian(buffer, offset + _updateSequenceOffset);
_updateSequenceArray = new ushort[_updateSequenceCount - 1];
for (int i = 0; i < _updateSequenceArray.Length; ++i)
{
_updateSequenceArray[i] = Utilities.ToUInt16LittleEndian(buffer, offset + _updateSequenceOffset + (2 * (i + 1)));
}
UnprotectBuffer(buffer, offset);
Read(buffer, offset);
}
public void ToBytes(byte[] buffer, int offset)
{
_updateSequenceOffset = Write(buffer, offset);
ProtectBuffer(buffer, offset);
Utilities.StringToBytes(_magic, buffer, offset + 0x00, 4);
Utilities.WriteBytesLittleEndian(_updateSequenceOffset, buffer, offset + 0x04);
Utilities.WriteBytesLittleEndian(_updateSequenceCount, buffer, offset + 0x06);
Utilities.WriteBytesLittleEndian(_updateSequenceNumber, buffer, offset + _updateSequenceOffset);
for (int i = 0; i < _updateSequenceArray.Length; ++i)
{
Utilities.WriteBytesLittleEndian(_updateSequenceArray[i], buffer, offset + _updateSequenceOffset + (2 * (i + 1)));
}
}
protected void Initialize(string magic, int sectorSize, int recordLength)
{
_magic = magic;
_sectorSize = sectorSize;
_updateSequenceCount = (ushort)(1 + Utilities.Ceil(recordLength, Sizes.Sector));
_updateSequenceNumber = 1;
_updateSequenceArray = new ushort[_updateSequenceCount - 1];
}
protected abstract void Read(byte[] buffer, int offset);
protected abstract ushort Write(byte[] buffer, int offset);
protected abstract int CalcSize();
private void UnprotectBuffer(byte[] buffer, int offset)
{
// First do validation check - make sure the USN matches on all sectors)
for (int i = 0; i < _updateSequenceArray.Length; ++i)
{
if (_updateSequenceNumber != Utilities.ToUInt16LittleEndian(buffer, offset + (Sizes.Sector * (i + 1)) - 2))
{
throw new IOException("Corrupt file system record found");
}
}
// Now replace the USNs with the actual data from the sequence array
for (int i = 0; i < _updateSequenceArray.Length; ++i)
{
Utilities.WriteBytesLittleEndian(_updateSequenceArray[i], buffer, offset + (Sizes.Sector * (i + 1)) - 2);
}
}
private void ProtectBuffer(byte[] buffer, int offset)
{
_updateSequenceNumber++;
// Read in the bytes that are replaced by the USN
for (int i = 0; i < _updateSequenceArray.Length; ++i)
{
_updateSequenceArray[i] = Utilities.ToUInt16LittleEndian(buffer, offset + (Sizes.Sector * (i + 1)) - 2);
}
// Overwrite the bytes that are replaced with the USN
for (int i = 0; i < _updateSequenceArray.Length; ++i)
{
Utilities.WriteBytesLittleEndian(_updateSequenceNumber, buffer, offset + (Sizes.Sector * (i + 1)) - 2);
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
//
// 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;
internal sealed class GenericFixupRecord : FixupRecordBase
{
private int _bytesPerSector;
private byte[] _content;
public GenericFixupRecord(int bytesPerSector)
: base(null, bytesPerSector)
{
_bytesPerSector = bytesPerSector;
}
public byte[] Content
{
get { return _content; }
}
protected override void Read(byte[] buffer, int offset)
{
_content = new byte[(UpdateSequenceCount - 1) * _bytesPerSector];
Array.Copy(buffer, offset, _content, 0, _content.Length);
}
protected override ushort Write(byte[] buffer, int offset)
{
throw new NotImplementedException();
}
protected override int CalcSize()
{
throw new NotImplementedException();
}
}
}
+485
View File
@@ -0,0 +1,485 @@
//
// 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.IO;
internal class Index : IDisposable
{
protected File _file;
protected string _name;
protected BiosParameterBlock _bpb;
private bool _isFileIndex;
private IComparer<byte[]> _comparer;
private IndexRoot _root;
private IndexNode _rootNode;
private Stream _indexStream;
private Bitmap _indexBitmap;
private ObjectCache<long, IndexBlock> _blockCache;
public Index(File file, string name, BiosParameterBlock bpb, UpperCase upCase)
{
_file = file;
_name = name;
_bpb = bpb;
_isFileIndex = name == "$I30";
_blockCache = new ObjectCache<long, IndexBlock>();
_root = _file.GetStream(AttributeType.IndexRoot, _name).GetContent<IndexRoot>();
_comparer = _root.GetCollator(upCase);
using (Stream s = _file.OpenStream(AttributeType.IndexRoot, _name, FileAccess.Read))
{
byte[] buffer = Utilities.ReadFully(s, (int)s.Length);
_rootNode = new IndexNode(WriteRootNodeToDisk, 0, this, true, buffer, IndexRoot.HeaderOffset);
// Give the attribute some room to breathe, so long as it doesn't squeeze others out
// BROKEN, BROKEN, BROKEN - how to figure this out? Query at the point of adding entries to the root node?
_rootNode.TotalSpaceAvailable += _file.MftRecordFreeSpace(AttributeType.IndexRoot, _name) - 100;
}
if (_file.StreamExists(AttributeType.IndexAllocation, _name))
{
_indexStream = _file.OpenStream(AttributeType.IndexAllocation, _name, FileAccess.ReadWrite);
}
if (_file.StreamExists(AttributeType.Bitmap, _name))
{
_indexBitmap = new Bitmap(_file.OpenStream(AttributeType.Bitmap, _name, FileAccess.ReadWrite), long.MaxValue);
}
}
private Index(AttributeType attrType, AttributeCollationRule collationRule, File file, string name, BiosParameterBlock bpb, UpperCase upCase)
{
_file = file;
_name = name;
_bpb = bpb;
_isFileIndex = name == "$I30";
_blockCache = new ObjectCache<long, IndexBlock>();
_file.CreateStream(AttributeType.IndexRoot, _name);
_root = new IndexRoot()
{
AttributeType = (uint)attrType,
CollationRule = collationRule,
IndexAllocationSize = (uint)bpb.IndexBufferSize,
RawClustersPerIndexRecord = bpb.RawIndexBufferSize
};
_comparer = _root.GetCollator(upCase);
_rootNode = new IndexNode(WriteRootNodeToDisk, 0, this, true, 32);
}
public IEnumerable<KeyValuePair<byte[], byte[]>> Entries
{
get
{
foreach (var entry in Enumerate(_rootNode))
{
yield return new KeyValuePair<byte[], byte[]>(entry.KeyBuffer, entry.DataBuffer);
}
}
}
public int Count
{
get
{
int i = 0;
foreach (var entry in Entries)
{
++i;
}
return i;
}
}
internal Stream AllocationStream
{
get { return _indexStream; }
}
internal uint IndexBufferSize
{
get { return _root.IndexAllocationSize; }
}
internal bool IsFileIndex
{
get { return _isFileIndex; }
}
public byte[] this[byte[] key]
{
get
{
byte[] value;
if (TryGetValue(key, out value))
{
return value;
}
else
{
throw new KeyNotFoundException();
}
}
set
{
IndexEntry oldEntry;
IndexNode node;
_rootNode.TotalSpaceAvailable = _rootNode.CalcSize() + _file.MftRecordFreeSpace(AttributeType.IndexRoot, _name);
if (_rootNode.TryFindEntry(key, out oldEntry, out node))
{
node.UpdateEntry(key, value);
}
else
{
_rootNode.AddEntry(key, value);
}
}
}
public static void Create(AttributeType attrType, AttributeCollationRule collationRule, File file, string name)
{
Index idx = new Index(attrType, collationRule, file, name, file.Context.BiosParameterBlock, file.Context.UpperCase);
idx.WriteRootNodeToDisk();
}
public void Dispose()
{
if (_indexBitmap != null)
{
_indexBitmap.Dispose();
_indexBitmap = null;
}
}
public IEnumerable<KeyValuePair<byte[], byte[]>> FindAll(IComparable<byte[]> query)
{
foreach (var entry in FindAllIn(query, _rootNode))
{
yield return new KeyValuePair<byte[], byte[]>(entry.KeyBuffer, entry.DataBuffer);
}
}
public bool ContainsKey(byte[] key)
{
byte[] value;
return TryGetValue(key, out value);
}
public bool Remove(byte[] key)
{
_rootNode.TotalSpaceAvailable = _rootNode.CalcSize() + _file.MftRecordFreeSpace(AttributeType.IndexRoot, _name);
IndexEntry overflowEntry;
bool found = _rootNode.RemoveEntry(key, out overflowEntry);
if (overflowEntry != null)
{
throw new IOException("Error removing entry, root overflowed");
}
return found;
}
public bool TryGetValue(byte[] key, out byte[] value)
{
IndexEntry entry;
IndexNode node;
if (_rootNode.TryFindEntry(key, out entry, out node))
{
value = entry.DataBuffer;
return true;
}
value = default(byte[]);
return false;
}
internal static string EntryAsString(IndexEntry entry, string fileName, string indexName)
{
IByteArraySerializable keyValue = null;
IByteArraySerializable dataValue = null;
// Try to guess the type of data in the key and data fields from the filename and index name
if (indexName == "$I30")
{
keyValue = new FileNameRecord();
dataValue = new FileRecordReference();
}
else if (fileName == "$ObjId" && indexName == "$O")
{
keyValue = new ObjectIds.IndexKey();
dataValue = new ObjectIdRecord();
}
else if (fileName == "$Reparse" && indexName == "$R")
{
keyValue = new ReparsePoints.Key();
dataValue = new ReparsePoints.Data();
}
else if (fileName == "$Quota")
{
if (indexName == "$O")
{
keyValue = new Quotas.OwnerKey();
dataValue = new Quotas.OwnerRecord();
}
else if (indexName == "$Q")
{
keyValue = new Quotas.OwnerRecord();
dataValue = new Quotas.QuotaRecord();
}
}
else if (fileName == "$Secure")
{
if (indexName == "$SII")
{
keyValue = new SecurityDescriptors.IdIndexKey();
dataValue = new SecurityDescriptors.IdIndexData();
}
else if (indexName == "$SDH")
{
keyValue = new SecurityDescriptors.HashIndexKey();
dataValue = new SecurityDescriptors.IdIndexData();
}
}
try
{
if (keyValue != null && dataValue != null)
{
keyValue.ReadFrom(entry.KeyBuffer, 0);
dataValue.ReadFrom(entry.DataBuffer, 0);
return "{" + keyValue + "-->" + dataValue + "}";
}
}
catch
{
return "{Parsing-Error}";
}
return "{Unknown-Index-Type}";
}
internal long IndexBlockVcnToPosition(long vcn)
{
if (vcn % _root.RawClustersPerIndexRecord != 0)
{
throw new NotSupportedException("Unexpected vcn (not a multiple of clusters-per-index-record): vcn=" + vcn + " rcpir=" + _root.RawClustersPerIndexRecord);
}
if (_bpb.BytesPerCluster <= _root.IndexAllocationSize)
{
return vcn * (long)_bpb.BytesPerCluster;
}
else
{
if (_root.RawClustersPerIndexRecord != 8)
{
throw new NotSupportedException("Unexpected RawClustersPerIndexRecord (multiple index blocks per cluster): " + _root.RawClustersPerIndexRecord);
}
return (vcn / _root.RawClustersPerIndexRecord) * _root.IndexAllocationSize;
}
}
internal bool ShrinkRoot()
{
if (_rootNode.Depose())
{
WriteRootNodeToDisk();
_rootNode.TotalSpaceAvailable = _rootNode.CalcSize() + _file.MftRecordFreeSpace(AttributeType.IndexRoot, _name);
return true;
}
return false;
}
internal IndexBlock GetSubBlock(IndexEntry parentEntry)
{
IndexBlock block = _blockCache[parentEntry.ChildrenVirtualCluster];
if (block == null)
{
block = new IndexBlock(this, false, parentEntry, _bpb);
_blockCache[parentEntry.ChildrenVirtualCluster] = block;
}
return block;
}
internal IndexBlock AllocateBlock(IndexEntry parentEntry)
{
if (_indexStream == null)
{
_file.CreateStream(AttributeType.IndexAllocation, _name);
_indexStream = _file.OpenStream(AttributeType.IndexAllocation, _name, FileAccess.ReadWrite);
}
if (_indexBitmap == null)
{
_file.CreateStream(AttributeType.Bitmap, _name);
_indexBitmap = new Bitmap(_file.OpenStream(AttributeType.Bitmap, _name, FileAccess.ReadWrite), long.MaxValue);
}
long idx = _indexBitmap.AllocateFirstAvailable(0);
parentEntry.ChildrenVirtualCluster = idx * Utilities.Ceil(_bpb.IndexBufferSize, _bpb.SectorsPerCluster * _bpb.BytesPerSector);
parentEntry.Flags |= IndexEntryFlags.Node;
IndexBlock block = IndexBlock.Initialize(this, false, parentEntry, _bpb);
_blockCache[parentEntry.ChildrenVirtualCluster] = block;
return block;
}
internal void FreeBlock(long vcn)
{
long idx = vcn / Utilities.Ceil(_bpb.IndexBufferSize, _bpb.SectorsPerCluster * _bpb.BytesPerSector);
_indexBitmap.MarkAbsent(idx);
_blockCache.Remove(vcn);
}
internal int Compare(byte[] x, byte[] y)
{
return _comparer.Compare(x, y);
}
internal void Dump(TextWriter writer, string prefix)
{
NodeAsString(writer, prefix, _rootNode, "R");
}
protected IEnumerable<IndexEntry> Enumerate(IndexNode node)
{
foreach (var focus in node.Entries)
{
if ((focus.Flags & IndexEntryFlags.Node) != 0)
{
IndexBlock block = GetSubBlock(focus);
foreach (var subEntry in Enumerate(block.Node))
{
yield return subEntry;
}
}
if ((focus.Flags & IndexEntryFlags.End) == 0)
{
yield return focus;
}
}
}
private IEnumerable<IndexEntry> FindAllIn(IComparable<byte[]> query, IndexNode node)
{
foreach (var focus in node.Entries)
{
bool searchChildren = true;
bool matches = false;
bool keepIterating = true;
if ((focus.Flags & IndexEntryFlags.End) == 0)
{
int compVal = query.CompareTo(focus.KeyBuffer);
if (compVal == 0)
{
matches = true;
}
else if (compVal > 0)
{
searchChildren = false;
}
else if (compVal < 0)
{
keepIterating = false;
}
}
if (searchChildren && (focus.Flags & IndexEntryFlags.Node) != 0)
{
IndexBlock block = GetSubBlock(focus);
foreach (var entry in FindAllIn(query, block.Node))
{
yield return entry;
}
}
if (matches)
{
yield return focus;
}
if (!keepIterating)
{
yield break;
}
}
}
private void WriteRootNodeToDisk()
{
_rootNode.Header.AllocatedSizeOfEntries = (uint)_rootNode.CalcSize();
byte[] buffer = new byte[_rootNode.Header.AllocatedSizeOfEntries + _root.Size];
_root.WriteTo(buffer, 0);
_rootNode.WriteTo(buffer, _root.Size);
using (Stream s = _file.OpenStream(AttributeType.IndexRoot, _name, FileAccess.Write))
{
s.Position = 0;
s.Write(buffer, 0, buffer.Length);
s.SetLength(s.Position);
}
}
private void NodeAsString(TextWriter writer, string prefix, IndexNode node, string id)
{
writer.WriteLine(prefix + id + ":");
foreach (var entry in node.Entries)
{
if ((entry.Flags & IndexEntryFlags.End) != 0)
{
writer.WriteLine(prefix + " E");
}
else
{
writer.WriteLine(prefix + " " + EntryAsString(entry, _file.BestName, _name));
}
if ((entry.Flags & IndexEntryFlags.Node) != 0)
{
NodeAsString(writer, prefix + " ", GetSubBlock(entry).Node, ":i" + entry.ChildrenVirtualCluster);
}
}
}
}
}
+114
View File
@@ -0,0 +1,114 @@
//
// 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.IO;
internal class IndexBlock : FixupRecordBase
{
/// <summary>
/// Size of meta-data placed at start of a block.
/// </summary>
private const int FieldSize = 0x18;
private ulong _logSequenceNumber;
private ulong _indexBlockVcn; // Virtual Cluster Number (maybe in sectors sometimes...?)
private IndexNode _node;
private Index _index;
private bool _isRoot;
private long _streamPosition;
public IndexBlock(Index index, bool isRoot, IndexEntry parentEntry, BiosParameterBlock bpb)
: base("INDX", bpb.BytesPerSector)
{
_index = index;
_isRoot = isRoot;
Stream stream = index.AllocationStream;
_streamPosition = index.IndexBlockVcnToPosition(parentEntry.ChildrenVirtualCluster);
stream.Position = _streamPosition;
byte[] buffer = Utilities.ReadFully(stream, (int)index.IndexBufferSize);
FromBytes(buffer, 0);
}
private IndexBlock(Index index, bool isRoot, long vcn, BiosParameterBlock bpb)
: base("INDX", bpb.BytesPerSector, bpb.IndexBufferSize)
{
_index = index;
_isRoot = isRoot;
_indexBlockVcn = (ulong)vcn;
_streamPosition = vcn * bpb.BytesPerSector * bpb.SectorsPerCluster;
_node = new IndexNode(WriteToDisk, UpdateSequenceSize, _index, isRoot, (uint)bpb.IndexBufferSize - FieldSize);
WriteToDisk();
}
public IndexNode Node
{
get { return _node; }
}
internal static IndexBlock Initialize(Index index, bool isRoot, IndexEntry parentEntry, BiosParameterBlock bpb)
{
return new IndexBlock(index, isRoot, parentEntry.ChildrenVirtualCluster, bpb);
}
internal void WriteToDisk()
{
byte[] buffer = new byte[_index.IndexBufferSize];
ToBytes(buffer, 0);
Stream stream = _index.AllocationStream;
stream.Position = _streamPosition;
stream.Write(buffer, 0, buffer.Length);
stream.Flush();
}
protected override void Read(byte[] buffer, int offset)
{
// Skip FixupRecord fields...
_logSequenceNumber = Utilities.ToUInt64LittleEndian(buffer, offset + 0x08);
_indexBlockVcn = Utilities.ToUInt64LittleEndian(buffer, offset + 0x10);
_node = new IndexNode(WriteToDisk, UpdateSequenceSize, _index, _isRoot, buffer, offset + FieldSize);
}
protected override ushort Write(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(_logSequenceNumber, buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian(_indexBlockVcn, buffer, offset + 0x10);
return (ushort)(FieldSize + Node.WriteTo(buffer, offset + FieldSize));
}
protected override int CalcSize()
{
throw new NotImplementedException();
}
}
}
+193
View File
@@ -0,0 +1,193 @@
//
// 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;
[Flags]
internal enum IndexEntryFlags : ushort
{
None = 0x00,
Node = 0x01,
End = 0x02
}
internal class IndexEntry
{
public const int EndNodeSize = 0x18;
protected IndexEntryFlags _flags;
protected long _vcn; // Only valid if Node flag set
protected byte[] _keyBuffer;
protected byte[] _dataBuffer;
private bool _isFileIndexEntry;
public IndexEntry(bool isFileIndexEntry)
{
_isFileIndexEntry = isFileIndexEntry;
}
public IndexEntry(IndexEntry toCopy, byte[] newKey, byte[] newData)
{
_isFileIndexEntry = toCopy._isFileIndexEntry;
_flags = toCopy._flags;
_vcn = toCopy._vcn;
_keyBuffer = newKey;
_dataBuffer = newData;
}
public IndexEntry(byte[] key, byte[] data, bool isFileIndexEntry)
{
_isFileIndexEntry = isFileIndexEntry;
_flags = IndexEntryFlags.None;
_keyBuffer = key;
_dataBuffer = data;
}
public byte[] KeyBuffer
{
get { return _keyBuffer; }
set { _keyBuffer = value; }
}
public byte[] DataBuffer
{
get { return _dataBuffer; }
set { _dataBuffer = value; }
}
public IndexEntryFlags Flags
{
get { return _flags; }
set { _flags = value; }
}
public long ChildrenVirtualCluster
{
get { return _vcn; }
set { _vcn = value; }
}
public virtual int Size
{
get
{
int size = 0x10; // start of variable data
if ((_flags & IndexEntryFlags.End) == 0)
{
size += _keyBuffer.Length;
size += IsFileIndexEntry ? 0 : _dataBuffer.Length;
}
size = Utilities.RoundUp(size, 8);
if ((_flags & IndexEntryFlags.Node) != 0)
{
size += 8;
}
return size;
}
}
protected bool IsFileIndexEntry
{
get { return _isFileIndexEntry; }
}
public virtual void Read(byte[] buffer, int offset)
{
ushort dataOffset = Utilities.ToUInt16LittleEndian(buffer, offset + 0x00);
ushort dataLength = Utilities.ToUInt16LittleEndian(buffer, offset + 0x02);
ushort length = Utilities.ToUInt16LittleEndian(buffer, offset + 0x08);
ushort keyLength = Utilities.ToUInt16LittleEndian(buffer, offset + 0x0A);
_flags = (IndexEntryFlags)Utilities.ToUInt16LittleEndian(buffer, offset + 0x0C);
if ((_flags & IndexEntryFlags.End) == 0)
{
_keyBuffer = new byte[keyLength];
Array.Copy(buffer, offset + 0x10, _keyBuffer, 0, keyLength);
if (IsFileIndexEntry)
{
// Special case, for file indexes, the MFT ref is held where the data offset & length go
_dataBuffer = new byte[8];
Array.Copy(buffer, offset + 0x00, _dataBuffer, 0, 8);
}
else
{
_dataBuffer = new byte[dataLength];
Array.Copy(buffer, offset + 0x10 + keyLength, _dataBuffer, 0, dataLength);
}
}
if ((_flags & IndexEntryFlags.Node) != 0)
{
_vcn = Utilities.ToInt64LittleEndian(buffer, offset + length - 8);
}
}
public virtual void WriteTo(byte[] buffer, int offset)
{
ushort length = (ushort)Size;
if ((_flags & IndexEntryFlags.End) == 0)
{
ushort keyLength = (ushort)_keyBuffer.Length;
if (IsFileIndexEntry)
{
Array.Copy(_dataBuffer, 0, buffer, offset + 0x00, 8);
}
else
{
ushort dataOffset = (ushort)(IsFileIndexEntry ? 0 : (0x10 + keyLength));
ushort dataLength = (ushort)_dataBuffer.Length;
Utilities.WriteBytesLittleEndian(dataOffset, buffer, offset + 0x00);
Utilities.WriteBytesLittleEndian(dataLength, buffer, offset + 0x02);
Array.Copy(_dataBuffer, 0, buffer, offset + dataOffset, _dataBuffer.Length);
}
Utilities.WriteBytesLittleEndian(keyLength, buffer, offset + 0x0A);
Array.Copy(_keyBuffer, 0, buffer, offset + 0x10, _keyBuffer.Length);
}
else
{
Utilities.WriteBytesLittleEndian((ushort)0, buffer, offset + 0x00); // dataOffset
Utilities.WriteBytesLittleEndian((ushort)0, buffer, offset + 0x02); // dataLength
Utilities.WriteBytesLittleEndian((ushort)0, buffer, offset + 0x0A); // keyLength
}
Utilities.WriteBytesLittleEndian(length, buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian((ushort)_flags, buffer, offset + 0x0C);
if ((_flags & IndexEntryFlags.Node) != 0)
{
Utilities.WriteBytesLittleEndian(_vcn, buffer, offset + length - 8);
}
}
}
}
+58
View File
@@ -0,0 +1,58 @@
//
// 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
{
internal class IndexHeader
{
public const int Size = 0x10;
public uint OffsetToFirstEntry;
public uint TotalSizeOfEntries;
public uint AllocatedSizeOfEntries;
public byte HasChildNodes;
public IndexHeader(uint allocatedSize)
{
AllocatedSizeOfEntries = allocatedSize;
}
public IndexHeader(byte[] data, int offset)
{
OffsetToFirstEntry = Utilities.ToUInt32LittleEndian(data, offset + 0x00);
TotalSizeOfEntries = Utilities.ToUInt32LittleEndian(data, offset + 0x04);
AllocatedSizeOfEntries = Utilities.ToUInt32LittleEndian(data, offset + 0x08);
HasChildNodes = data[offset + 0x0C];
}
internal void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(OffsetToFirstEntry, buffer, offset + 0x00);
Utilities.WriteBytesLittleEndian(TotalSizeOfEntries, buffer, offset + 0x04);
Utilities.WriteBytesLittleEndian(AllocatedSizeOfEntries, buffer, offset + 0x08);
buffer[offset + 0x0C] = HasChildNodes;
buffer[offset + 0x0D] = 0;
buffer[offset + 0x0E] = 0;
buffer[offset + 0x0F] = 0;
}
}
}
+588
View File
@@ -0,0 +1,588 @@
//
// 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.IO;
internal delegate void IndexNodeSaveFn();
internal class IndexNode
{
private IndexNodeSaveFn _store;
private int _storageOverhead;
private long _totalSpaceAvailable;
private IndexHeader _header;
private Index _index;
private bool _isRoot;
private List<IndexEntry> _entries;
public IndexNode(IndexNodeSaveFn store, int storeOverhead, Index index, bool isRoot, uint allocatedSize)
{
_store = store;
_storageOverhead = storeOverhead;
_index = index;
_isRoot = isRoot;
_header = new IndexHeader(allocatedSize);
_totalSpaceAvailable = allocatedSize;
IndexEntry endEntry = new IndexEntry(_index.IsFileIndex);
endEntry.Flags |= IndexEntryFlags.End;
_entries = new List<IndexEntry>();
_entries.Add(endEntry);
_header.OffsetToFirstEntry = (uint)(IndexHeader.Size + storeOverhead);
_header.TotalSizeOfEntries = (uint)(_header.OffsetToFirstEntry + endEntry.Size);
}
public IndexNode(IndexNodeSaveFn store, int storeOverhead, Index index, bool isRoot, byte[] buffer, int offset)
{
_store = store;
_storageOverhead = storeOverhead;
_index = index;
_isRoot = isRoot;
_header = new IndexHeader(buffer, offset + 0);
_totalSpaceAvailable = _header.AllocatedSizeOfEntries;
_entries = new List<IndexEntry>();
int pos = (int)_header.OffsetToFirstEntry;
while (pos < _header.TotalSizeOfEntries)
{
IndexEntry entry = new IndexEntry(index.IsFileIndex);
entry.Read(buffer, offset + pos);
_entries.Add(entry);
if ((entry.Flags & IndexEntryFlags.End) != 0)
{
break;
}
pos += entry.Size;
}
}
public IndexHeader Header
{
get { return _header; }
}
public IEnumerable<IndexEntry> Entries
{
get { return _entries; }
}
internal long TotalSpaceAvailable
{
get { return _totalSpaceAvailable; }
set { _totalSpaceAvailable = value; }
}
private long SpaceFree
{
get
{
long entriesTotal = 0;
for (int i = 0; i < _entries.Count; ++i)
{
entriesTotal += _entries[i].Size;
}
int firstEntryOffset = Utilities.RoundUp(IndexHeader.Size + _storageOverhead, 8);
return _totalSpaceAvailable - (entriesTotal + firstEntryOffset);
}
}
public void AddEntry(byte[] key, byte[] data)
{
IndexEntry overflowEntry = AddEntry(new IndexEntry(key, data, _index.IsFileIndex));
if (overflowEntry != null)
{
throw new IOException("Error adding entry - root overflowed");
}
}
public void UpdateEntry(byte[] key, byte[] data)
{
for (int i = 0; i < _entries.Count; ++i)
{
var focus = _entries[i];
int compVal = _index.Compare(key, focus.KeyBuffer);
if (compVal == 0)
{
IndexEntry newEntry = new IndexEntry(focus, key, data);
if (_entries[i].Size != newEntry.Size)
{
throw new NotImplementedException("Changing index entry sizes");
}
_entries[i] = newEntry;
_store();
return;
}
}
throw new IOException("No such index entry");
}
public bool TryFindEntry(byte[] key, out IndexEntry entry, out IndexNode node)
{
foreach (var focus in _entries)
{
if ((focus.Flags & IndexEntryFlags.End) != 0)
{
if ((focus.Flags & IndexEntryFlags.Node) != 0)
{
IndexBlock subNode = _index.GetSubBlock(focus);
return subNode.Node.TryFindEntry(key, out entry, out node);
}
break;
}
else
{
int compVal = _index.Compare(key, focus.KeyBuffer);
if (compVal == 0)
{
entry = focus;
node = this;
return true;
}
else if (compVal < 0 && (focus.Flags & (IndexEntryFlags.End | IndexEntryFlags.Node)) != 0)
{
IndexBlock subNode = _index.GetSubBlock(focus);
return subNode.Node.TryFindEntry(key, out entry, out node);
}
}
}
entry = null;
node = null;
return false;
}
public virtual ushort WriteTo(byte[] buffer, int offset)
{
bool haveSubNodes = false;
uint totalEntriesSize = 0;
foreach (var entry in _entries)
{
totalEntriesSize += (uint)entry.Size;
haveSubNodes |= (entry.Flags & IndexEntryFlags.Node) != 0;
}
_header.OffsetToFirstEntry = (uint)Utilities.RoundUp(IndexHeader.Size + _storageOverhead, 8);
_header.TotalSizeOfEntries = totalEntriesSize + _header.OffsetToFirstEntry;
_header.HasChildNodes = (byte)(haveSubNodes ? 1 : 0);
_header.WriteTo(buffer, offset + 0);
int pos = (int)_header.OffsetToFirstEntry;
foreach (var entry in _entries)
{
entry.WriteTo(buffer, offset + pos);
pos += entry.Size;
}
return IndexHeader.Size;
}
public int CalcEntriesSize()
{
int totalEntriesSize = 0;
foreach (var entry in _entries)
{
totalEntriesSize += entry.Size;
}
return totalEntriesSize;
}
public virtual int CalcSize()
{
int firstEntryOffset = Utilities.RoundUp(IndexHeader.Size + _storageOverhead, 8);
return firstEntryOffset + CalcEntriesSize();
}
public int GetEntry(byte[] key, out bool exactMatch)
{
for (int i = 0; i < _entries.Count; ++i)
{
var focus = _entries[i];
int compVal;
if ((focus.Flags & IndexEntryFlags.End) != 0)
{
exactMatch = false;
return i;
}
else
{
compVal = _index.Compare(key, focus.KeyBuffer);
if (compVal <= 0)
{
exactMatch = compVal == 0;
return i;
}
}
}
throw new IOException("Corrupt index node - no End entry");
}
public bool RemoveEntry(byte[] key, out IndexEntry newParentEntry)
{
bool exactMatch;
int entryIndex = GetEntry(key, out exactMatch);
IndexEntry entry = _entries[entryIndex];
if (exactMatch)
{
if ((entry.Flags & IndexEntryFlags.Node) != 0)
{
IndexNode childNode = _index.GetSubBlock(entry).Node;
IndexEntry rLeaf = childNode.FindLargestLeaf();
byte[] newKey = rLeaf.KeyBuffer;
byte[] newData = rLeaf.DataBuffer;
IndexEntry newEntry;
childNode.RemoveEntry(newKey, out newEntry);
entry.KeyBuffer = newKey;
entry.DataBuffer = newData;
if (newEntry != null)
{
InsertEntryThisNode(newEntry);
}
newEntry = LiftNode(entryIndex);
if (newEntry != null)
{
InsertEntryThisNode(newEntry);
}
newEntry = PopulateEnd();
if (newEntry != null)
{
InsertEntryThisNode(newEntry);
}
// New entry could be larger than old, so may need
// to divide this node...
newParentEntry = EnsureNodeSize();
}
else
{
_entries.RemoveAt(entryIndex);
newParentEntry = null;
}
_store();
return true;
}
else if ((entry.Flags & IndexEntryFlags.Node) != 0)
{
IndexNode childNode = _index.GetSubBlock(entry).Node;
IndexEntry newEntry;
if (childNode.RemoveEntry(key, out newEntry))
{
if (newEntry != null)
{
InsertEntryThisNode(newEntry);
}
newEntry = LiftNode(entryIndex);
if (newEntry != null)
{
InsertEntryThisNode(newEntry);
}
newEntry = PopulateEnd();
if (newEntry != null)
{
InsertEntryThisNode(newEntry);
}
// New entry could be larger than old, so may need
// to divide this node...
newParentEntry = EnsureNodeSize();
_store();
return true;
}
}
newParentEntry = null;
return false;
}
/// <summary>
/// Only valid on the root node, this method moves all entries into a
/// single child node.
/// </summary>
/// <returns>Whether any changes were made.</returns>
internal bool Depose()
{
if (!_isRoot)
{
throw new InvalidOperationException("Only valid on root node");
}
if (_entries.Count == 1)
{
return false;
}
IndexEntry newRootEntry = new IndexEntry(_index.IsFileIndex);
newRootEntry.Flags = IndexEntryFlags.End;
IndexBlock newBlock = _index.AllocateBlock(newRootEntry);
// Set the deposed entries into the new node. Note we updated the parent
// pointers first, because it's possible SetEntries may need to further
// divide the entries to fit into nodes. We mustn't overwrite any changes.
newBlock.Node.SetEntries(_entries, 0, _entries.Count);
_entries.Clear();
_entries.Add(newRootEntry);
return true;
}
/// <summary>
/// Removes redundant nodes (that contain only an 'End' entry).
/// </summary>
/// <param name="entryIndex">The index of the entry that may have a redundant child.</param>
/// <returns>An entry that needs to be promoted to the parent node (if any).</returns>
private IndexEntry LiftNode(int entryIndex)
{
if ((_entries[entryIndex].Flags & IndexEntryFlags.Node) != 0)
{
IndexNode childNode = _index.GetSubBlock(_entries[entryIndex]).Node;
if (childNode._entries.Count == 1)
{
long freeBlock = _entries[entryIndex].ChildrenVirtualCluster;
_entries[entryIndex].Flags = (_entries[entryIndex].Flags & ~IndexEntryFlags.Node) | (childNode._entries[0].Flags & IndexEntryFlags.Node);
_entries[entryIndex].ChildrenVirtualCluster = childNode._entries[0].ChildrenVirtualCluster;
_index.FreeBlock(freeBlock);
}
if ((_entries[entryIndex].Flags & (IndexEntryFlags.Node | IndexEntryFlags.End)) == 0)
{
IndexEntry entry = _entries[entryIndex];
_entries.RemoveAt(entryIndex);
IndexNode nextNode = _index.GetSubBlock(_entries[entryIndex]).Node;
return nextNode.AddEntry(entry);
}
}
return null;
}
private IndexEntry PopulateEnd()
{
if (_entries.Count > 1
&& _entries[_entries.Count - 1].Flags == IndexEntryFlags.End
&& (_entries[_entries.Count - 2].Flags & IndexEntryFlags.Node) != 0)
{
IndexEntry old = _entries[_entries.Count - 2];
_entries.RemoveAt(_entries.Count - 2);
_entries[_entries.Count - 1].ChildrenVirtualCluster = old.ChildrenVirtualCluster;
_entries[_entries.Count - 1].Flags |= IndexEntryFlags.Node;
old.ChildrenVirtualCluster = 0;
old.Flags = IndexEntryFlags.None;
return _index.GetSubBlock(_entries[_entries.Count - 1]).Node.AddEntry(old);
}
return null;
}
private void InsertEntryThisNode(IndexEntry newEntry)
{
bool exactMatch;
int index = GetEntry(newEntry.KeyBuffer, out exactMatch);
if (exactMatch)
{
throw new InvalidOperationException("Entry already exists");
}
else
{
_entries.Insert(index, newEntry);
}
}
private IndexEntry AddEntry(IndexEntry newEntry)
{
bool exactMatch;
int index = GetEntry(newEntry.KeyBuffer, out exactMatch);
if (exactMatch)
{
throw new InvalidOperationException("Entry already exists");
}
if ((_entries[index].Flags & IndexEntryFlags.Node) != 0)
{
IndexEntry ourNewEntry = _index.GetSubBlock(_entries[index]).Node.AddEntry(newEntry);
if (ourNewEntry == null)
{
// No change to this node
return null;
}
InsertEntryThisNode(ourNewEntry);
}
else
{
_entries.Insert(index, newEntry);
}
// If there wasn't enough space, we may need to
// divide this node
IndexEntry newParentEntry = EnsureNodeSize();
_store();
return newParentEntry;
}
private IndexEntry EnsureNodeSize()
{
// While the node is too small to hold the entries, we need to reduce
// the number of entries.
if (SpaceFree < 0)
{
if (_isRoot)
{
Depose();
}
else
{
return Divide();
}
}
return null;
}
/// <summary>
/// Finds the largest leaf entry in this tree.
/// </summary>
/// <returns>The index entry of the largest leaf.</returns>
private IndexEntry FindLargestLeaf()
{
if ((_entries[_entries.Count - 1].Flags & IndexEntryFlags.Node) != 0)
{
return _index.GetSubBlock(_entries[_entries.Count - 1]).Node.FindLargestLeaf();
}
else if (_entries.Count > 1 && (_entries[_entries.Count - 2].Flags & IndexEntryFlags.Node) == 0)
{
return _entries[_entries.Count - 2];
}
else
{
throw new IOException("Invalid index node found");
}
}
/// <summary>
/// Only valid on non-root nodes, this method divides the node in two,
/// adding the new node to the current parent.
/// </summary>
/// <returns>An entry that needs to be promoted to the parent node (if any).</returns>
private IndexEntry Divide()
{
int midEntryIdx = _entries.Count / 2;
IndexEntry midEntry = _entries[midEntryIdx];
// The terminating entry (aka end) for the new node
IndexEntry newTerm = new IndexEntry(_index.IsFileIndex);
newTerm.Flags |= IndexEntryFlags.End;
// The set of entries in the new node
List<IndexEntry> newEntries = new List<IndexEntry>(midEntryIdx + 1);
for (int i = 0; i < midEntryIdx; ++i)
{
newEntries.Add(_entries[i]);
}
newEntries.Add(newTerm);
// Copy the node pointer from the elected 'mid' entry to the new node
if ((midEntry.Flags & IndexEntryFlags.Node) != 0)
{
newTerm.ChildrenVirtualCluster = midEntry.ChildrenVirtualCluster;
newTerm.Flags |= IndexEntryFlags.Node;
}
// Set the new entries into the new node
IndexBlock newBlock = _index.AllocateBlock(midEntry);
// Set the entries into the new node. Note we updated the parent
// pointers first, because it's possible SetEntries may need to further
// divide the entries to fit into nodes. We mustn't overwrite any changes.
newBlock.Node.SetEntries(newEntries, 0, newEntries.Count);
// Forget about the entries moved into the new node, and the entry about
// to be promoted as the new node's pointer
_entries.RemoveRange(0, midEntryIdx + 1);
// Promote the old mid entry
return midEntry;
}
private void SetEntries(IList<IndexEntry> newEntries, int offset, int count)
{
_entries.Clear();
for (int i = 0; i < count; ++i)
{
_entries.Add(newEntries[i + offset]);
}
// Add an end entry, if not present
if (count == 0 || (_entries[_entries.Count - 1].Flags & IndexEntryFlags.End) == 0)
{
IndexEntry end = new IndexEntry(_index.IsFileIndex);
end.Flags = IndexEntryFlags.End;
_entries.Add(end);
}
// Ensure the node isn't over-filled
if (SpaceFree < 0)
{
throw new IOException("Error setting node entries - oversized for node");
}
// Persist the new entries to disk
_store();
}
}
}
+297
View File
@@ -0,0 +1,297 @@
//
// 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.IO;
internal sealed class IndexRoot : IByteArraySerializable, IDiagnosticTraceable
{
public const int HeaderOffset = 0x10;
private uint _attrType;
private AttributeCollationRule _collationRule;
private uint _indexAllocationEntrySize;
private byte _rawClustersPerIndexRecord;
public uint AttributeType
{
get { return _attrType; }
set { _attrType = value; }
}
public AttributeCollationRule CollationRule
{
get { return _collationRule; }
set { _collationRule = value; }
}
public uint IndexAllocationSize
{
get { return _indexAllocationEntrySize; }
set { _indexAllocationEntrySize = value; }
}
public byte RawClustersPerIndexRecord
{
get { return _rawClustersPerIndexRecord; }
set { _rawClustersPerIndexRecord = value; }
}
public int Size
{
get { return 16; }
}
public IComparer<byte[]> GetCollator(UpperCase upCase)
{
switch (_collationRule)
{
case AttributeCollationRule.Filename:
return new FileNameComparer(upCase);
case AttributeCollationRule.SecurityHash:
return new SecurityHashComparer();
case AttributeCollationRule.UnsignedLong:
return new UnsignedLongComparer();
case AttributeCollationRule.MultipleUnsignedLongs:
return new MultipleUnsignedLongComparer();
case AttributeCollationRule.Sid:
return new SidComparer();
default:
throw new NotImplementedException();
}
}
public int ReadFrom(byte[] buffer, int offset)
{
_attrType = Utilities.ToUInt32LittleEndian(buffer, 0x00);
_collationRule = (AttributeCollationRule)Utilities.ToUInt32LittleEndian(buffer, 0x04);
_indexAllocationEntrySize = Utilities.ToUInt32LittleEndian(buffer, 0x08);
_rawClustersPerIndexRecord = buffer[0x0C];
return 16;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(_attrType, buffer, 0);
Utilities.WriteBytesLittleEndian((uint)_collationRule, buffer, 0x04);
Utilities.WriteBytesLittleEndian(_indexAllocationEntrySize, buffer, 0x08);
Utilities.WriteBytesLittleEndian(_rawClustersPerIndexRecord, buffer, 0x0C);
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + " Attr Type: " + _attrType);
writer.WriteLine(indent + " Collation Rule: " + _collationRule);
writer.WriteLine(indent + " Index Alloc Size: " + _indexAllocationEntrySize);
writer.WriteLine(indent + " Raw Clusters Per Record: " + _rawClustersPerIndexRecord);
}
private sealed class SecurityHashComparer : IComparer<byte[]>
{
public int Compare(byte[] x, byte[] y)
{
if (x == null && y == null)
{
return 0;
}
else if (y == null)
{
return -1;
}
else if (x == null)
{
return 1;
}
uint xHash = Utilities.ToUInt32LittleEndian(x, 0);
uint yHash = Utilities.ToUInt32LittleEndian(y, 0);
if (xHash < yHash)
{
return -1;
}
else if (xHash > yHash)
{
return 1;
}
uint xId = Utilities.ToUInt32LittleEndian(x, 4);
uint yId = Utilities.ToUInt32LittleEndian(y, 4);
if (xId < yId)
{
return -1;
}
else if (xId > yId)
{
return 1;
}
else
{
return 0;
}
}
}
private sealed class UnsignedLongComparer : IComparer<byte[]>
{
public int Compare(byte[] x, byte[] y)
{
if (x == null && y == null)
{
return 0;
}
else if (y == null)
{
return -1;
}
else if (x == null)
{
return 1;
}
uint xVal = Utilities.ToUInt32LittleEndian(x, 0);
uint yVal = Utilities.ToUInt32LittleEndian(y, 0);
if (xVal < yVal)
{
return -1;
}
else if (xVal > yVal)
{
return 1;
}
return 0;
}
}
private sealed class MultipleUnsignedLongComparer : IComparer<byte[]>
{
public int Compare(byte[] x, byte[] y)
{
for (int i = 0; i < x.Length / 4; ++i)
{
if (x == null && y == null)
{
return 0;
}
else if (y == null)
{
return -1;
}
else if (x == null)
{
return 1;
}
uint xVal = Utilities.ToUInt32LittleEndian(x, i * 4);
uint yVal = Utilities.ToUInt32LittleEndian(y, i * 4);
if (xVal < yVal)
{
return -1;
}
else if (xVal > yVal)
{
return 1;
}
}
return 0;
}
}
private sealed class FileNameComparer : IComparer<byte[]>
{
private UpperCase _stringComparer;
public FileNameComparer(UpperCase upCase)
{
_stringComparer = upCase;
}
public int Compare(byte[] x, byte[] y)
{
if (x == null && y == null)
{
return 0;
}
else if (y == null)
{
return -1;
}
else if (x == null)
{
return 1;
}
byte xFnLen = x[0x40];
byte yFnLen = y[0x40];
return _stringComparer.Compare(x, 0x42, xFnLen * 2, y, 0x42, yFnLen * 2);
}
}
private sealed class SidComparer : IComparer<byte[]>
{
public int Compare(byte[] x, byte[] y)
{
if (x == null && y == null)
{
return 0;
}
else if (y == null)
{
return -1;
}
else if (x == null)
{
return 1;
}
int toComp = Math.Min(x.Length, y.Length);
for (int i = 0; i < toComp; ++i)
{
int val = ((int)x[i]) - ((int)y[i]);
if (val != 0)
{
return val;
}
}
if (x.Length < y.Length)
{
return -1;
}
else if (x.Length > y.Length)
{
return 1;
}
return 0;
}
}
}
}
+160
View File
@@ -0,0 +1,160 @@
//
// 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;
internal class IndexView<K, D>
where K : IByteArraySerializable, new()
where D : IByteArraySerializable, new()
{
private Index _index;
public IndexView(Index index)
{
_index = index;
}
public int Count
{
get { return _index.Count; }
}
public IEnumerable<KeyValuePair<K, D>> Entries
{
get
{
foreach (var entry in _index.Entries)
{
yield return new KeyValuePair<K, D>(Convert<K>(entry.Key), Convert<D>(entry.Value));
}
}
}
public D this[K key]
{
get
{
return Convert<D>(_index[Unconvert(key)]);
}
set
{
_index[Unconvert(key)] = Unconvert<D>(value);
}
}
public IEnumerable<KeyValuePair<K, D>> FindAll(IComparable<byte[]> query)
{
foreach (var entry in _index.FindAll(query))
{
yield return new KeyValuePair<K, D>(Convert<K>(entry.Key), Convert<D>(entry.Value));
}
}
public KeyValuePair<K, D> FindFirst(IComparable<byte[]> query)
{
foreach (var entry in FindAll(query))
{
return entry;
}
return default(KeyValuePair<K, D>);
}
public IEnumerable<KeyValuePair<K, D>> FindAll(IComparable<K> query)
{
foreach (var entry in _index.FindAll(new ComparableConverter(query)))
{
yield return new KeyValuePair<K, D>(Convert<K>(entry.Key), Convert<D>(entry.Value));
}
}
public KeyValuePair<K, D> FindFirst(IComparable<K> query)
{
foreach (var entry in FindAll(query))
{
return entry;
}
return default(KeyValuePair<K, D>);
}
public bool TryGetValue(K key, out D data)
{
byte[] value;
if (_index.TryGetValue(Unconvert(key), out value))
{
data = Convert<D>(value);
return true;
}
else
{
data = default(D);
return false;
}
}
public bool ContainsKey(K key)
{
return _index.ContainsKey(Unconvert(key));
}
public void Remove(K key)
{
_index.Remove(Unconvert(key));
}
private static T Convert<T>(byte[] data)
where T : IByteArraySerializable, new()
{
T result = new T();
result.ReadFrom(data, 0);
return result;
}
private static byte[] Unconvert<T>(T value)
where T : IByteArraySerializable, new()
{
byte[] buffer = new byte[value.Size];
value.WriteTo(buffer, 0);
return buffer;
}
private class ComparableConverter : IComparable<byte[]>
{
private IComparable<K> _wrapped;
public ComparableConverter(IComparable<K> toWrap)
{
_wrapped = toWrap;
}
public int CompareTo(byte[] other)
{
return _wrapped.CompareTo(Convert<K>(other));
}
}
}
}
@@ -0,0 +1,53 @@
//
// 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.Internals
{
using System;
/// <summary>
/// Flags indicating how an attribute's content is stored on disk.
/// </summary>
[Flags]
public enum AttributeFlags
{
/// <summary>
/// The data is stored in linear form.
/// </summary>
None = 0x0000,
/// <summary>
/// The data is compressed.
/// </summary>
Compressed = 0x0001,
/// <summary>
/// The data is encrypted.
/// </summary>
Encrypted = 0x4000,
/// <summary>
/// The data is stored in sparse form.
/// </summary>
Sparse = 0x8000
}
}
@@ -0,0 +1,67 @@
//
// 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.Internals
{
using System.Collections.Generic;
/// <summary>
/// List of attributes for files that are split over multiple Master File Table entries.
/// </summary>
/// <remarks>
/// <para>
/// Files with lots of attribute data (for example that have become very fragmented) contain
/// this attribute in their 'base' Master File Table entry. This attribute acts as an index,
/// indicating for each attribute in the file, which Master File Table entry contains the
/// attribute.
/// </para>
/// </remarks>
public sealed class AttributeListAttribute : GenericAttribute
{
private AttributeList _list;
internal AttributeListAttribute(INtfsContext context, AttributeRecord record)
: base(context, record)
{
byte[] content = Utilities.ReadAll(Content);
_list = new AttributeList();
_list.ReadFrom(content, 0);
}
/// <summary>
/// Gets the entries in this attribute list.
/// </summary>
public ICollection<AttributeListEntry> Entries
{
get
{
List<AttributeListEntry> entries = new List<AttributeListEntry>();
foreach (var record in _list)
{
entries.Add(new AttributeListEntry(record));
}
return entries;
}
}
}
}
@@ -0,0 +1,93 @@
//
// 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.Internals
{
/// <summary>
/// Represents an entry in an AttributeList attribute.
/// </summary>
/// <remarks>Each instance of this class points to the actual Master File Table
/// entry that contains the attribute. It is used for files split over multiple
/// Master File Table entries.</remarks>
public sealed class AttributeListEntry
{
private AttributeListRecord _record;
internal AttributeListEntry(AttributeListRecord record)
{
_record = record;
}
/// <summary>
/// Gets the type of the attribute.
/// </summary>
public AttributeType AttributeType
{
get { return _record.Type; }
}
/// <summary>
/// Gets the name of the attribute (if any).
/// </summary>
public string AttributeName
{
get { return _record.Name; }
}
/// <summary>
/// Gets the first cluster represented in this attribute (normally 0).
/// </summary>
/// <remarks>
/// <para>
/// For very fragmented files, it can be necessary to split a single attribute
/// over multiple Master File Table entries. This is achieved with multiple attributes
/// with the same name and type (one per Master File Table entry), with this field
/// determining the logical order of the attributes.
/// </para>
/// <para>
/// The number is the first 'virtual' cluster present (i.e. divide the file's content
/// into 'cluster' sized chunks, this is the first of those clusters logically
/// represented in the attribute).
/// </para>
/// </remarks>
public long FirstFileCluster
{
get { return (long)_record.StartVcn; }
}
/// <summary>
/// Gets the Master File Table entry that contains the attribute.
/// </summary>
public MasterFileTableReference MasterFileTableEntry
{
get { return new MasterFileTableReference(_record.BaseFileReference); }
}
/// <summary>
/// Gets the identifier of the attribute.
/// </summary>
public int AttributeIdentifier
{
get { return _record.AttributeId; }
}
}
}
+56
View File
@@ -0,0 +1,56 @@
//
// 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.
//
using System;
namespace DiscUtils.Ntfs.Internals
{
/// <summary>
/// Flags indicating the state of a Master File Table entry.
/// </summary>
/// <remarks>
/// Used to filter entries in the Master File Table.
/// </remarks>
[Flags]
public enum EntryState
{
/// <summary>
/// No entries match.
/// </summary>
None = 0,
/// <summary>
/// The entry is currently in use.
/// </summary>
InUse = 1,
/// <summary>
/// The entry is currently not in use.
/// </summary>
NotInUse = 2,
/// <summary>
/// All entries match.
/// </summary>
All = 3
}
}
+56
View File
@@ -0,0 +1,56 @@
//
// 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.Internals
{
using System;
/// <summary>
/// Flags indicating the state of a Master File Table entry.
/// </summary>
/// <remarks>
/// Used to filter entries in the Master File Table.
/// </remarks>
[Flags]
public enum EntryStates
{
/// <summary>
/// No entries match.
/// </summary>
None = 0,
/// <summary>
/// The entry is currently in use.
/// </summary>
InUse = 1,
/// <summary>
/// The entry is currently not in use.
/// </summary>
NotInUse = 2,
/// <summary>
/// All entries match.
/// </summary>
All = 3
}
}
@@ -0,0 +1,145 @@
//
// 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.Internals
{
using System;
/// <summary>
/// Representation of an NTFS File Name attribute.
/// </summary>
/// <remarks>
/// <para>
/// Each Master File Table entry (MFT Entry) has one of these attributes for each
/// hard link. Files with a long name and a short name will have at least two of
/// these attributes.</para>
/// <para>
/// The details in this attribute may be inconsistent with similar information in
/// the StandardInformationAttribute for a file. The StandardInformation is
/// definitive, this attribute holds a 'cache' of the information.
/// </para>
/// </remarks>
public sealed class FileNameAttribute : GenericAttribute
{
private FileNameRecord _fnr;
internal FileNameAttribute(INtfsContext context, AttributeRecord record)
: base(context, record)
{
byte[] content = Utilities.ReadAll(Content);
_fnr = new FileNameRecord();
_fnr.ReadFrom(content, 0);
}
/// <summary>
/// Gets the reference to the parent directory.
/// </summary>
/// <remarks>
/// This attribute stores the name of a file within a directory, this field
/// provides the link back to the directory.
/// </remarks>
public MasterFileTableReference ParentDirectory
{
get { return new MasterFileTableReference(_fnr.ParentDirectory); }
}
/// <summary>
/// Gets the creation time of the file.
/// </summary>
public DateTime CreationTime
{
get { return _fnr.CreationTime; }
}
/// <summary>
/// Gets the modification time of the file.
/// </summary>
public DateTime ModificationTime
{
get { return _fnr.ModificationTime; }
}
/// <summary>
/// Gets the last time the Master File Table entry for the file was changed.
/// </summary>
public DateTime MasterFileTableChangedTime
{
get { return _fnr.MftChangedTime; }
}
/// <summary>
/// Gets the last access time of the file.
/// </summary>
public DateTime LastAccessTime
{
get { return _fnr.LastAccessTime; }
}
/// <summary>
/// Gets the amount of disk space allocated for the file.
/// </summary>
public long AllocatedSize
{
get { return (long)_fnr.AllocatedSize; }
}
/// <summary>
/// Gets the amount of data stored in the file.
/// </summary>
public long RealSize
{
get { return (long)_fnr.RealSize; }
}
/// <summary>
/// Gets the attributes of the file, as stored by NTFS.
/// </summary>
public NtfsFileAttributes FileAttributes
{
get { return (NtfsFileAttributes)_fnr.Flags; }
}
/// <summary>
/// Gets the extended attributes size, or a reparse tag, depending on the nature of the file.
/// </summary>
public long ExtendedAttributesSizeOrReparsePointTag
{
get { return (long)_fnr.EASizeOrReparsePointTag; }
}
/// <summary>
/// Gets the namespace of the FileName property.
/// </summary>
public NtfsNamespace FileNameNamespace
{
get { return (NtfsNamespace)_fnr.FileNameNamespace; }
}
/// <summary>
/// Gets the name of the file within the parent directory.
/// </summary>
public string FileName
{
get { return _fnr.FileName; }
}
}
}
@@ -0,0 +1,117 @@
//
// 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.Internals
{
/// <summary>
/// Base class for all attributes within Master File Table entries.
/// </summary>
/// <remarks>
/// More specialized base classes are provided for known attribute types.
/// </remarks>
public abstract class GenericAttribute
{
private INtfsContext _context;
private AttributeRecord _record;
internal GenericAttribute(INtfsContext context, AttributeRecord record)
{
_context = context;
_record = record;
}
/// <summary>
/// Gets the name of the attribute (if any).
/// </summary>
public string Name
{
get { return _record.Name; }
}
/// <summary>
/// Gets the type of the attribute.
/// </summary>
public AttributeType AttributeType
{
get { return _record.AttributeType; }
}
/// <summary>
/// Gets the unique id of the attribute.
/// </summary>
public int Identifier
{
get { return _record.AttributeId; }
}
/// <summary>
/// Gets a value indicating whether the attribute content is stored in the MFT record itself.
/// </summary>
public bool IsResident
{
get { return !_record.IsNonResident; }
}
/// <summary>
/// Gets the flags indicating how the content of the attribute is stored.
/// </summary>
public AttributeFlags Flags
{
get { return (AttributeFlags)_record.Flags; }
}
/// <summary>
/// Gets the amount of valid data in the attribute's content.
/// </summary>
public long ContentLength
{
get { return _record.DataLength; }
}
/// <summary>
/// Gets a buffer that can access the content of the attribute.
/// </summary>
public IBuffer Content
{
get
{
IBuffer rawBuffer = _record.GetReadOnlyDataBuffer(_context);
return new SubBuffer(rawBuffer, 0, _record.DataLength);
}
}
internal static GenericAttribute FromAttributeRecord(INtfsContext context, AttributeRecord record)
{
switch (record.AttributeType)
{
case AttributeType.AttributeList:
return new AttributeListAttribute(context, record);
case AttributeType.FileName:
return new FileNameAttribute(context, record);
case AttributeType.StandardInformation:
return new StandardInformationAttribute(context, record);
default:
return new UnknownAttribute(context, record);
}
}
}
}
+154
View File
@@ -0,0 +1,154 @@
//
// 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.Internals
{
using System.Collections.Generic;
using InternalMasterFileTable = DiscUtils.Ntfs.MasterFileTable;
/// <summary>
/// Provides read-only access to the Master File Table of an NTFS file system.
/// </summary>
public sealed class MasterFileTable
{
/// <summary>
/// Index of the Master File Table itself.
/// </summary>
public const long MasterFileTableIndex = 0;
/// <summary>
/// Index of the Master File Table Mirror file.
/// </summary>
public const long MasterFileTableMirrorIndex = 1;
/// <summary>
/// Index of the Log file.
/// </summary>
public const long LogFileIndex = 2;
/// <summary>
/// Index of the Volume file.
/// </summary>
public const long VolumeIndex = 3;
/// <summary>
/// Index of the Attribute Definition file.
/// </summary>
public const long AttributeDefinitionIndex = 4;
/// <summary>
/// Index of the Root Directory.
/// </summary>
public const long RootDirectoryIndex = 5;
/// <summary>
/// Index of the Bitmap file.
/// </summary>
public const long BitmapIndex = 6;
/// <summary>
/// Index of the Boot sector(s).
/// </summary>
public const long BootIndex = 7;
/// <summary>
/// Index of the Bad Cluster file.
/// </summary>
public const long BadClusterIndex = 8;
/// <summary>
/// Index of the Security Descriptor file.
/// </summary>
public const long SecureIndex = 9;
/// <summary>
/// Index of the Uppercase mapping file.
/// </summary>
public const long UppercaseIndex = 10;
/// <summary>
/// Index of the Optional Extensions directory.
/// </summary>
public const long ExtendDirectoryIndex = 11;
/// <summary>
/// First index available for 'normal' files.
/// </summary>
private const uint FirstNormalFileIndex = 24;
private INtfsContext _context;
private InternalMasterFileTable _mft;
internal MasterFileTable(INtfsContext context, InternalMasterFileTable mft)
{
_context = context;
_mft = mft;
}
/// <summary>
/// Gets an entry by index.
/// </summary>
/// <param name="index">The index of the entry.</param>
/// <returns>The entry.</returns>
public MasterFileTableEntry this[long index]
{
get
{
FileRecord mftRecord = _mft.GetRecord(index, true, true);
if (mftRecord != null)
{
return new MasterFileTableEntry(_context, mftRecord);
}
else
{
return null;
}
}
}
/// <summary>
/// Enumerates all entries.
/// </summary>
/// <param name="filter">Filter controlling which entries are returned.</param>
/// <returns>An enumeration of entries matching the filter.</returns>
public IEnumerable<MasterFileTableEntry> GetEntries(EntryStates filter)
{
foreach (var record in _mft.Records)
{
EntryStates state;
if ((record.Flags & FileRecordFlags.InUse) != 0)
{
state = EntryStates.InUse;
}
else
{
state = EntryStates.NotInUse;
}
if ((state & filter) != 0)
{
yield return new MasterFileTableEntry(_context, record);
}
}
}
}
}
@@ -0,0 +1,10 @@
namespace DiscUtils.Ntfs.Internals
{
using System;
using System.Collections.Generic;
using System.Text;
public sealed class MasterFileTableAttribute
{
}
}
@@ -0,0 +1,135 @@
//
// 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.Internals
{
using System.Collections.Generic;
/// <summary>
/// An entry within the Master File Table.
/// </summary>
public sealed class MasterFileTableEntry
{
private INtfsContext _context;
private FileRecord _fileRecord;
internal MasterFileTableEntry(INtfsContext context, FileRecord fileRecord)
{
_context = context;
_fileRecord = fileRecord;
}
/// <summary>
/// Gets the index of this entry in the Master File Table.
/// </summary>
public long Index
{
get { return _fileRecord.LoadedIndex; }
}
/// <summary>
/// Gets the change identifier that is updated each time the file is modified by Windows, relates to the NTFS log file.
/// </summary>
/// <remarks>
/// The NTFS log file provides journalling, preventing meta-data corruption in the event of a system crash.
/// </remarks>
public long LogFileSequenceNumber
{
get { return (long)_fileRecord.LogFileSequenceNumber; }
}
/// <summary>
/// Gets the revision number of the entry.
/// </summary>
/// <remarks>
/// Each time an entry is allocated or de-allocated, this number is incremented by one.
/// </remarks>
public int SequenceNumber
{
get { return _fileRecord.SequenceNumber; }
}
/// <summary>
/// Gets the number of hard links referencing this file.
/// </summary>
public int HardLinkCount
{
get { return _fileRecord.HardLinkCount; }
}
/// <summary>
/// Gets the flags indicating the nature of the entry.
/// </summary>
public MasterFileTableEntryFlags Flags
{
get { return (MasterFileTableEntryFlags)_fileRecord.Flags; }
}
/// <summary>
/// Gets the identity of the base entry for files split over multiple entries.
/// </summary>
/// <remarks>
/// All entries that form part of the same file have the same value for
/// this property.
/// </remarks>
public MasterFileTableReference BaseRecordReference
{
get { return new MasterFileTableReference(_fileRecord.BaseFile); }
}
/// <summary>
/// Gets the next attribute identity that will be allocated.
/// </summary>
public int NextAttributeId
{
get { return _fileRecord.NextAttributeId; }
}
/// <summary>
/// Gets the index of this entry in the Master File Table (as stored in the entry itself).
/// </summary>
/// <remarks>
/// Note - older versions of Windows did not store this value, so it may be Zero.
/// </remarks>
public long SelfIndex
{
get { return _fileRecord.MasterFileTableIndex; }
}
/// <summary>
/// Gets the attributes contained in this entry.
/// </summary>
public ICollection<GenericAttribute> Attributes
{
get
{
List<GenericAttribute> result = new List<GenericAttribute>();
foreach (var attr in _fileRecord.Attributes)
{
result.Add(GenericAttribute.FromAttributeRecord(_context, attr));
}
return result;
}
}
}
}
@@ -0,0 +1,58 @@
//
// 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.Internals
{
using System;
/// <summary>
/// Flags indicating the nature of a Master File Table entry.
/// </summary>
[Flags]
public enum MasterFileTableEntryFlags : int
{
/// <summary>
/// Default value.
/// </summary>
None = 0x0000,
/// <summary>
/// The entry is currently in use.
/// </summary>
InUse = 0x0001,
/// <summary>
/// The entry is for a directory (rather than a file).
/// </summary>
IsDirectory = 0x0002,
/// <summary>
/// The entry is for a file that forms parts of the NTFS meta-data.
/// </summary>
IsMetaFile = 0x0004,
/// <summary>
/// The entry contains index attributes.
/// </summary>
HasViewIndex = 0x0008
}
}
@@ -0,0 +1,71 @@
//
// 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.Internals
{
using System.Collections;
using System.Collections.Generic;
using System.IO;
public sealed class MasterFileTableRecord
{
private FileRecord _fileRecord;
internal MasterFileTableRecord(FileRecord fileRecord)
{
_fileRecord = fileRecord;
}
/// <summary>
/// Changes each time the file is modified by Windows, relates to the NTFS journal.
/// </summary>
public long JournalSequenceNumber
{
get { return (long)_fileRecord.LogFileSequenceNumber; }
}
public int SequenceNumber
{
get { return _fileRecord.SequenceNumber; }
}
public int HardLinkCount
{
get { return _fileRecord.HardLinkCount; }
}
public MasterFileTableRecordFlags Flags
{
get { return (MasterFileTableRecordFlags)_fileRecord.Flags; }
}
public MasterFileTableReference BaseRecordReference
{
get { return new MasterFileTableReference(_fileRecord.BaseFile); }
}
public int NextAttributeId
{
get { return _fileRecord.NextAttributeId; }
}
}
}
@@ -0,0 +1,36 @@
//
// 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.Internals
{
using System;
[Flags]
public enum MasterFileTableRecordFlags : int
{
None = 0x0000,
InUse = 0x0001,
IsDirectory = 0x0002,
IsMetaFile = 0x0004,
HasViewIndex = 0x0008
}
}
@@ -0,0 +1,103 @@
//
// 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.Internals
{
/// <summary>
/// A reference to a Master File Table entry.
/// </summary>
public struct MasterFileTableReference
{
private FileRecordReference _ref;
internal MasterFileTableReference(FileRecordReference recordRef)
{
_ref = recordRef;
}
/// <summary>
/// Gets the index of the referred entry in the Master File Table.
/// </summary>
public long RecordIndex
{
get { return _ref.MftIndex; }
}
/// <summary>
/// Gets the revision number of the entry.
/// </summary>
/// <remarks>
/// This value prevents accidental reference to an entry - it will get out
/// of sync with the actual entry if the entry is re-allocated or de-allocated.
/// </remarks>
public int RecordSequenceNumber
{
get { return _ref.SequenceNumber; }
}
/// <summary>
/// Compares to instances for equality.
/// </summary>
/// <param name="a">The first instance to compare.</param>
/// <param name="b">The second instance to compare.</param>
/// <returns><code>true</code> if the instances are equivalent, else <code>false</code>.</returns>
public static bool operator ==(MasterFileTableReference a, MasterFileTableReference b)
{
return a._ref == b._ref;
}
/// <summary>
/// Compares to instances for equality.
/// </summary>
/// <param name="a">The first instance to compare.</param>
/// <param name="b">The second instance to compare.</param>
/// <returns><code>true</code> if the instances are not equivalent, else <code>false</code>.</returns>
public static bool operator !=(MasterFileTableReference a, MasterFileTableReference b)
{
return a._ref != b._ref;
}
/// <summary>
/// Compares another object for equality.
/// </summary>
/// <param name="obj">The object to compare.</param>
/// <returns><code>true</code> if the other object is equivalent, else <code>false</code>.</returns>
public override bool Equals(object obj)
{
if (obj == null || !(obj is MasterFileTableReference))
{
return false;
}
return _ref == ((MasterFileTableReference)obj)._ref;
}
/// <summary>
/// Gets a hash code for this instance.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return _ref.GetHashCode();
}
}
}
@@ -0,0 +1,113 @@
//
// 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.Internals
{
using System;
/// <summary>
/// File attributes as stored natively by NTFS.
/// </summary>
[Flags]
public enum NtfsFileAttributes
{
/// <summary>
/// No attributes.
/// </summary>
None = 0x00000000,
/// <summary>
/// The file is read-only.
/// </summary>
ReadOnly = 0x00000001,
/// <summary>
/// The file is hidden.
/// </summary>
Hidden = 0x00000002,
/// <summary>
/// The file is part of the Operating System.
/// </summary>
System = 0x00000004,
/// <summary>
/// The file should be archived.
/// </summary>
Archive = 0x00000020,
/// <summary>
/// The file is actually a device.
/// </summary>
Device = 0x00000040,
/// <summary>
/// The file is a 'normal' file.
/// </summary>
Normal = 0x00000080,
/// <summary>
/// The file is a temporary file.
/// </summary>
Temporary = 0x00000100,
/// <summary>
/// The file content is stored in sparse form.
/// </summary>
Sparse = 0x00000200,
/// <summary>
/// The file has a reparse point attached.
/// </summary>
ReparsePoint = 0x00000400,
/// <summary>
/// The file content is stored compressed.
/// </summary>
Compressed = 0x00000800,
/// <summary>
/// The file is an 'offline' file.
/// </summary>
Offline = 0x00001000,
/// <summary>
/// The file is not indexed.
/// </summary>
NotIndexed = 0x00002000,
/// <summary>
/// The file content is encrypted.
/// </summary>
Encrypted = 0x00004000,
/// <summary>
/// The file is actually a directory.
/// </summary>
Directory = 0x10000000,
/// <summary>
/// The file has an index attribute.
/// </summary>
IndexView = 0x20000000
}
}
+56
View File
@@ -0,0 +1,56 @@
//
// 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.Internals
{
using System;
/// <summary>
/// The known NTFS namespaces.
/// </summary>
/// <remarks>
/// NTFS has multiple namespaces, indicating whether a name is the
/// long name for a file, the short name for a file, both, or none.
/// </remarks>
public enum NtfsNamespace
{
/// <summary>
/// Posix namespace (i.e. long name).
/// </summary>
Posix = 0,
/// <summary>
/// Windows long file name.
/// </summary>
Win32 = 1,
/// <summary>
/// DOS (8.3) file name.
/// </summary>
Dos = 2,
/// <summary>
/// File name that is both the long name and the DOS (8.3) name.
/// </summary>
Win32AndDos = 3
}
}
@@ -0,0 +1,146 @@
//
// 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.Internals
{
using System;
/// <summary>
/// Representation of an NTFS File Name attribute.
/// </summary>
/// <para>
/// The details in this attribute may be inconsistent with similar information in
/// the FileNameAttribute(s) for a file. This attribute is definitive, the
/// FileNameAttribute attribute holds a 'cache' of some of the information.
/// </para>
public sealed class StandardInformationAttribute : GenericAttribute
{
private StandardInformation _si;
internal StandardInformationAttribute(INtfsContext context, AttributeRecord record)
: base(context, record)
{
byte[] content = Utilities.ReadAll(Content);
_si = new StandardInformation();
_si.ReadFrom(content, 0);
}
/// <summary>
/// Gets the creation time of the file.
/// </summary>
public DateTime CreationTime
{
get { return _si.CreationTime; }
}
/// <summary>
/// Gets the modification time of the file.
/// </summary>
public DateTime ModificationTime
{
get { return _si.ModificationTime; }
}
/// <summary>
/// Gets the last time the Master File Table entry for the file was changed.
/// </summary>
public DateTime MasterFileTableChangedTime
{
get { return _si.MftChangedTime; }
}
/// <summary>
/// Gets the last access time of the file.
/// </summary>
public DateTime LastAccessTime
{
get { return _si.LastAccessTime; }
}
/// <summary>
/// Gets the attributes of the file, as stored by NTFS.
/// </summary>
public NtfsFileAttributes FileAttributes
{
get { return (NtfsFileAttributes)_si.FileAttributes; }
}
/// <summary>
/// Gets the maximum number of file versions (normally 0).
/// </summary>
public long MaxVersions
{
get { return _si.MaxVersions; }
}
/// <summary>
/// Gets the version number of the file (normally 0).
/// </summary>
public long Version
{
get { return _si.Version; }
}
/// <summary>
/// Gets the Unknown.
/// </summary>
public long ClassId
{
get { return _si.ClassId; }
}
/// <summary>
/// Gets the owner identity, for the purposes of quota allocation.
/// </summary>
public long OwnerId
{
get { return _si.OwnerId; }
}
/// <summary>
/// Gets the identifier of the Security Descriptor for this file.
/// </summary>
/// <remarks>
/// Security Descriptors are stored in the \$Secure meta-data file.
/// </remarks>
public long SecurityId
{
get { return _si.SecurityId; }
}
/// <summary>
/// Gets the amount charged to the owners quota for this file.
/// </summary>
public long QuotaCharged
{
get { return (long)_si.QuotaCharged; }
}
/// <summary>
/// Gets the last update sequence number of the file (relates to the user-readable journal).
/// </summary>
public long JournalSequenceNumber
{
get { return (long)_si.UpdateSequenceNumber; }
}
}
}
@@ -0,0 +1,32 @@
//
// 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.Internals
{
internal sealed class UnknownAttribute : GenericAttribute
{
public UnknownAttribute(INtfsContext context, AttributeRecord record)
: base(context, record)
{
}
}
}
+314
View File
@@ -0,0 +1,314 @@
//
// 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.
//
//
// Contributions by bsobel:
// - Compression algorithm distantly derived from Puyo tools (BSD license)*
// - Decompression adjusted to support variety of block sizes
//
// (*) Puyo tools implements a different LZ-style algorithm
//
namespace DiscUtils.Ntfs
{
using System;
using DiscUtils.Compression;
/// <summary>
/// Implementation of the LZNT1 algorithm used for compressing NTFS files.
/// </summary>
/// <remarks>
/// Due to apparent bugs in Window's LZNT1 decompressor, it is <b>strongly</b> recommended that
/// only the block size of 4096 is used. Other block sizes corrupt data on decompression.
/// </remarks>
internal sealed class LZNT1 : BlockCompressor
{
private const ushort SubBlockIsCompressedFlag = 0x8000;
private const ushort SubBlockSizeMask = 0x0fff;
// LZNT1 appears to ignore the actual block size requested, most likely due to
// a bug in the decompressor, which assumes 4KB block size. To be bug-compatible,
// we assume each block is 4KB on decode also.
private const int FixedBlockSize = 0x1000;
private static byte[] s_compressionBits = CalcCompressionBits();
public LZNT1()
{
BlockSize = 4096;
}
public override CompressionResult Compress(byte[] source, int sourceOffset, int sourceLength, byte[] compressed, int compressedOffset, ref int compressedLength)
{
uint sourcePointer = 0;
uint sourceCurrentBlock = 0;
uint destPointer = 0;
// Set up the Lz Compression Dictionary
LzWindowDictionary lzDictionary = new LzWindowDictionary();
bool nonZeroDataFound = false;
for (int subBlock = 0; subBlock < sourceLength; subBlock += BlockSize)
{
lzDictionary.MinMatchAmount = 3;
sourceCurrentBlock = sourcePointer;
uint decompressedSize = (uint)Math.Min(sourceLength - subBlock, BlockSize);
uint compressedSize = 0;
// Start compression
uint headerPosition = destPointer;
compressed[compressedOffset + destPointer] = compressed[compressedOffset + destPointer + 1] = 0;
destPointer += 2;
while (sourcePointer - subBlock < decompressedSize)
{
if (destPointer + 1 >= compressedLength)
{
return CompressionResult.Incompressible;
}
byte bitFlag = 0x0;
uint flagPosition = destPointer;
compressed[compressedOffset + destPointer] = bitFlag; // It will be filled in later
compressedSize++;
destPointer++;
for (int i = 0; i < 8; i++)
{
int lengthBits = 16 - s_compressionBits[sourcePointer - subBlock];
ushort lengthMask = (ushort)((1 << s_compressionBits[sourcePointer - subBlock]) - 1);
lzDictionary.MaxMatchAmount = Math.Min(1 << lengthBits, BlockSize - 1);
int[] lzSearchMatch = lzDictionary.Search(source, sourceOffset + subBlock, (uint)(sourcePointer - subBlock), decompressedSize);
if (lzSearchMatch[1] > 0)
{
// There is a compression match
if (destPointer + 2 >= compressedLength)
{
return CompressionResult.Incompressible;
}
bitFlag |= (byte)(1 << i);
int rawOffset = lzSearchMatch[0];
int rawLength = lzSearchMatch[1];
int convertedOffset = (rawOffset - 1) << lengthBits;
int convertedSize = (rawLength - 3) & ((1 << lengthMask) - 1);
ushort convertedData = (ushort)(convertedOffset | convertedSize);
Utilities.WriteBytesLittleEndian(convertedData, compressed, compressedOffset + (int)destPointer);
lzDictionary.AddEntryRange(source, sourceOffset + subBlock, (int)(sourcePointer - subBlock), lzSearchMatch[1]);
sourcePointer += (uint)lzSearchMatch[1];
destPointer += 2;
compressedSize += 2;
}
else
{
// There wasn't a match
if (destPointer + 1 >= compressedLength)
{
return CompressionResult.Incompressible;
}
bitFlag |= (byte)(0 << i);
if (source[sourceOffset + sourcePointer] != 0)
{
nonZeroDataFound = true;
}
compressed[compressedOffset + destPointer] = source[sourceOffset + sourcePointer];
lzDictionary.AddEntry(source, sourceOffset + subBlock, (int)(sourcePointer - subBlock));
sourcePointer++;
destPointer++;
compressedSize++;
}
// Check for out of bounds
if (sourcePointer - subBlock >= decompressedSize)
{
break;
}
}
// Write the real flag.
compressed[compressedOffset + flagPosition] = bitFlag;
}
// If compressed size >= block size just store block
if (compressedSize >= BlockSize)
{
// Set the header to indicate non-compressed block
Utilities.WriteBytesLittleEndian((ushort)(0x3000 | (BlockSize - 1)), compressed, compressedOffset + (int)headerPosition);
Array.Copy(source, (int)sourceOffset + sourceCurrentBlock, compressed, compressedOffset + headerPosition + 2, BlockSize);
destPointer = (uint)(headerPosition + 2 + BlockSize);
// Make sure decompression stops by setting the next two bytes to null, prevents us from having to
// clear the rest of the array.
compressed[destPointer] = 0;
compressed[destPointer + 1] = 0;
}
else
{
// Set the header to indicate compressed and the right length
Utilities.WriteBytesLittleEndian((ushort)(0xb000 | (compressedSize - 1)), compressed, compressedOffset + (int)headerPosition);
}
lzDictionary.Reset();
}
if (destPointer >= sourceLength)
{
compressedLength = 0;
return CompressionResult.Incompressible;
}
else if (nonZeroDataFound)
{
compressedLength = (int)destPointer;
return CompressionResult.Compressed;
}
else
{
compressedLength = 0;
return CompressionResult.AllZeros;
}
}
public override int Decompress(byte[] source, int sourceOffset, int sourceLength, byte[] decompressed, int decompressedOffset)
{
int sourceIdx = 0;
int destIdx = 0;
while (sourceIdx < sourceLength)
{
ushort header = Utilities.ToUInt16LittleEndian(source, sourceOffset + sourceIdx);
sourceIdx += 2;
// Look for null-terminating sub-block header
if (header == 0)
{
break;
}
if ((header & SubBlockIsCompressedFlag) == 0)
{
int blockSize = (header & SubBlockSizeMask) + 1;
Array.Copy(source, sourceOffset + sourceIdx, decompressed, decompressedOffset + destIdx, blockSize);
sourceIdx += blockSize;
destIdx += blockSize;
}
else
{
// compressed
int destSubBlockStart = destIdx;
int srcSubBlockEnd = sourceIdx + (header & SubBlockSizeMask) + 1;
while (sourceIdx < srcSubBlockEnd)
{
byte tag = source[sourceOffset + sourceIdx];
++sourceIdx;
for (int token = 0; token < 8; ++token)
{
// We might have hit the end of the sub block whilst still working though
// a tag - abort if we have...
if (sourceIdx >= srcSubBlockEnd)
{
break;
}
if ((tag & 1) == 0)
{
if (decompressedOffset + destIdx >= decompressed.Length)
{
return destIdx;
}
decompressed[decompressedOffset + destIdx] = source[sourceOffset + sourceIdx];
++destIdx;
++sourceIdx;
}
else
{
ushort lengthBits = (ushort)(16 - s_compressionBits[destIdx - destSubBlockStart]);
ushort lengthMask = (ushort)((1 << lengthBits) - 1);
ushort phraseToken = Utilities.ToUInt16LittleEndian(source, sourceOffset + sourceIdx);
sourceIdx += 2;
int destBackAddr = destIdx - (phraseToken >> lengthBits) - 1;
int length = (phraseToken & lengthMask) + 3;
for (int i = 0; i < length; ++i)
{
decompressed[decompressedOffset + destIdx++] = decompressed[decompressedOffset + destBackAddr++];
}
}
tag >>= 1;
}
}
// Bug-compatible - if we decompressed less than 4KB, jump to next 4KB boundary. If
// that would leave less than a 4KB remaining, abort with data decompressed so far.
if (decompressedOffset + destIdx + FixedBlockSize > decompressed.Length)
{
return destIdx;
}
else if (destIdx < destSubBlockStart + FixedBlockSize)
{
int skip = (destSubBlockStart + FixedBlockSize) - destIdx;
Array.Clear(decompressed, decompressedOffset + destIdx, skip);
destIdx += skip;
}
}
}
return destIdx;
}
private static byte[] CalcCompressionBits()
{
byte[] result = new byte[4096];
byte offsetBits = 0;
int y = 0x10;
for (int x = 0; x < result.Length; x++)
{
result[x] = (byte)(4 + offsetBits);
if (x == y)
{
y <<= 1;
offsetBits++;
}
}
return result;
}
}
}
+142
View File
@@ -0,0 +1,142 @@
//
// 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.
//
//
// Contributed by bsobel:
// - Derived from Puyo tools (BSD license)
//
namespace DiscUtils.Ntfs
{
using System;
using System.Collections.Generic;
internal sealed class LzWindowDictionary
{
/// <summary>
/// Index of locations of each possible byte value within the compression window.
/// </summary>
private List<int>[] _offsetList;
public LzWindowDictionary()
{
Initalize();
// Build the index list, so Lz compression will become significantly faster
_offsetList = new List<int>[0x100];
for (int i = 0; i < _offsetList.Length; i++)
{
_offsetList[i] = new List<int>();
}
}
public int MinMatchAmount { get; set; }
public int MaxMatchAmount { get; set; }
private int BlockSize { get; set; }
public void Reset()
{
Initalize();
for (int i = 0; i < _offsetList.Length; i++)
{
_offsetList[i].Clear();
}
}
public int[] Search(byte[] decompressedData, int decompressedDataOffset, uint index, uint length)
{
RemoveOldEntries(decompressedData[decompressedDataOffset + index]); // Remove old entries for this index
int[] match = new int[] { 0, 0 };
if (index < 1 || length - index < MinMatchAmount)
{
// Can't find matches if there isn't enough data
return match;
}
for (int i = 0; i < _offsetList[decompressedData[decompressedDataOffset + index]].Count; i++)
{
int matchStart = _offsetList[decompressedData[decompressedDataOffset + index]][i];
int matchSize = 1;
if (index - matchStart > BlockSize)
{
break;
}
int maxMatchSize = (int)Math.Min(Math.Min(MaxMatchAmount, BlockSize), Math.Min(length - index, length - matchStart));
while (matchSize < maxMatchSize && decompressedData[decompressedDataOffset + index + matchSize] == decompressedData[decompressedDataOffset + matchStart + matchSize])
{
matchSize++;
}
if (matchSize >= MinMatchAmount && matchSize > match[1])
{
// This is a good match
match = new int[] { (int)(index - matchStart), matchSize };
if (matchSize == MaxMatchAmount)
{
// Don't look for more matches
break;
}
}
}
// Return the real match (or the default 0:0 match).
return match;
}
// Add entries
public void AddEntry(byte[] decompressedData, int decompressedDataOffset, int index)
{
_offsetList[decompressedData[decompressedDataOffset + index]].Add(index);
}
public void AddEntryRange(byte[] decompressedData, int decompressedDataOffset, int index, int length)
{
for (int i = 0; i < length; i++)
{
AddEntry(decompressedData, decompressedDataOffset, index + i);
}
}
private void Initalize()
{
MinMatchAmount = 3;
MaxMatchAmount = 18;
BlockSize = 4096;
}
private void RemoveOldEntries(byte index)
{
while (_offsetList[index].Count > 256)
{
_offsetList[index].RemoveAt(0);
}
}
}
}
+522
View File
@@ -0,0 +1,522 @@
//
// 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;
}
}
}
}
+60
View File
@@ -0,0 +1,60 @@
//
// 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.Security.AccessControl;
/// <summary>
/// Options controlling how new NTFS files are created.
/// </summary>
public sealed class NewFileOptions
{
/// <summary>
/// Initializes a new instance of the NewFileOptions class.
/// </summary>
public NewFileOptions()
{
Compressed = null;
CreateShortNames = null;
SecurityDescriptor = null;
}
/// <summary>
/// Gets or sets whether the new file should be compressed.
/// </summary>
/// <remarks>The default (<c>null</c>) value indicates the file system default behaviour applies.</remarks>
public bool? Compressed { get; set; }
/// <summary>
/// Gets or sets whether a short name should be created for the file.
/// </summary>
/// <remarks>The default (<c>null</c>) value indicates the file system default behaviour applies.</remarks>
public bool? CreateShortNames { get; set; }
/// <summary>
/// Gets or sets the security descriptor that to set for the new file.
/// </summary>
/// <remarks>The default (<c>null</c>) value indicates the security descriptor is inherited.</remarks>
public RawSecurityDescriptor SecurityDescriptor { get; set; }
}
}
@@ -0,0 +1,353 @@
//
// 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.IO;
internal class NonResidentAttributeBuffer : NonResidentDataBuffer
{
private File _file;
private NtfsAttribute _attribute;
public NonResidentAttributeBuffer(File file, NtfsAttribute attribute)
: base(file.Context, CookRuns(attribute), file.IndexInMft == MasterFileTable.MftIndex)
{
_file = file;
_attribute = attribute;
switch (attribute.Flags & (AttributeFlags.Compressed | AttributeFlags.Sparse))
{
case AttributeFlags.Sparse:
_activeStream = new SparseClusterStream(_attribute, _rawStream);
break;
case AttributeFlags.Compressed:
_activeStream = new CompressedClusterStream(_context, _attribute, _rawStream);
break;
case AttributeFlags.None:
_activeStream = _rawStream;
break;
default:
throw new NotImplementedException("Unhandled attribute type '" + attribute.Flags + "'");
}
}
public override bool CanWrite
{
get { return _context.RawStream.CanWrite && _file != null; }
}
public override long Capacity
{
get { return PrimaryAttributeRecord.DataLength; }
}
private NonResidentAttributeRecord PrimaryAttributeRecord
{
get { return _attribute.PrimaryRecord as NonResidentAttributeRecord; }
}
public void AlignVirtualClusterCount()
{
_file.MarkMftRecordDirty();
_activeStream.ExpandToClusters(Utilities.Ceil(_attribute.Length, _bytesPerCluster), (NonResidentAttributeRecord)_attribute.LastExtent, false);
}
public override void SetCapacity(long value)
{
if (!CanWrite)
{
throw new IOException("Attempt to change length of file not opened for write");
}
if (value == Capacity)
{
return;
}
_file.MarkMftRecordDirty();
long newClusterCount = Utilities.Ceil(value, _bytesPerCluster);
if (value < Capacity)
{
Truncate(value);
}
else
{
_activeStream.ExpandToClusters(newClusterCount, (NonResidentAttributeRecord)_attribute.LastExtent, true);
PrimaryAttributeRecord.AllocatedLength = _cookedRuns.NextVirtualCluster * _bytesPerCluster;
}
PrimaryAttributeRecord.DataLength = value;
if (PrimaryAttributeRecord.InitializedDataLength > value)
{
PrimaryAttributeRecord.InitializedDataLength = value;
}
_cookedRuns.CollapseRuns();
}
public override void Write(long pos, byte[] buffer, int offset, int count)
{
if (!CanWrite)
{
throw new IOException("Attempt to write to file not opened for write");
}
if (count == 0)
{
return;
}
if (pos + count > Capacity)
{
SetCapacity(pos + count);
}
// Write zeros from end of current initialized data to the start of the new write
if (pos > PrimaryAttributeRecord.InitializedDataLength)
{
InitializeData(pos);
}
int allocatedClusters = 0;
long focusPos = pos;
while (focusPos < pos + count)
{
long vcn = focusPos / _bytesPerCluster;
long remaining = (pos + count) - focusPos;
long clusterOffset = focusPos - (vcn * _bytesPerCluster);
if (vcn * _bytesPerCluster != focusPos || remaining < _bytesPerCluster)
{
// Unaligned or short write
int toWrite = (int)Math.Min(remaining, _bytesPerCluster - clusterOffset);
_activeStream.ReadClusters(vcn, 1, _ioBuffer, 0);
Array.Copy(buffer, offset + (focusPos - pos), _ioBuffer, clusterOffset, toWrite);
allocatedClusters += _activeStream.WriteClusters(vcn, 1, _ioBuffer, 0);
focusPos += toWrite;
}
else
{
// Aligned, full cluster writes...
int fullClusters = (int)(remaining / _bytesPerCluster);
allocatedClusters += _activeStream.WriteClusters(vcn, fullClusters, buffer, (int)(offset + (focusPos - pos)));
focusPos += fullClusters * _bytesPerCluster;
}
}
if (pos + count > PrimaryAttributeRecord.InitializedDataLength)
{
_file.MarkMftRecordDirty();
PrimaryAttributeRecord.InitializedDataLength = pos + count;
}
if (pos + count > PrimaryAttributeRecord.DataLength)
{
_file.MarkMftRecordDirty();
PrimaryAttributeRecord.DataLength = pos + count;
}
if ((_attribute.Flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0)
{
PrimaryAttributeRecord.CompressedDataSize += allocatedClusters * _bytesPerCluster;
}
_cookedRuns.CollapseRuns();
}
public override void Clear(long pos, int count)
{
if (!CanWrite)
{
throw new IOException("Attempt to erase bytes from file not opened for write");
}
if (count == 0)
{
return;
}
if (pos + count > Capacity)
{
SetCapacity(pos + count);
}
_file.MarkMftRecordDirty();
// Write zeros from end of current initialized data to the start of the new write
if (pos > PrimaryAttributeRecord.InitializedDataLength)
{
InitializeData(pos);
}
int releasedClusters = 0;
long focusPos = pos;
while (focusPos < pos + count)
{
long vcn = focusPos / _bytesPerCluster;
long remaining = (pos + count) - focusPos;
long clusterOffset = focusPos - (vcn * _bytesPerCluster);
if (vcn * _bytesPerCluster != focusPos || remaining < _bytesPerCluster)
{
// Unaligned or short write
int toClear = (int)Math.Min(remaining, _bytesPerCluster - clusterOffset);
if (_activeStream.IsClusterStored(vcn))
{
_activeStream.ReadClusters(vcn, 1, _ioBuffer, 0);
Array.Clear(_ioBuffer, (int)clusterOffset, toClear);
releasedClusters -= _activeStream.WriteClusters(vcn, 1, _ioBuffer, 0);
}
focusPos += toClear;
}
else
{
// Aligned, full cluster clears...
int fullClusters = (int)(remaining / _bytesPerCluster);
releasedClusters += _activeStream.ClearClusters(vcn, fullClusters);
focusPos += fullClusters * _bytesPerCluster;
}
}
if (pos + count > PrimaryAttributeRecord.InitializedDataLength)
{
PrimaryAttributeRecord.InitializedDataLength = pos + count;
}
if (pos + count > PrimaryAttributeRecord.DataLength)
{
PrimaryAttributeRecord.DataLength = pos + count;
}
if ((_attribute.Flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0)
{
PrimaryAttributeRecord.CompressedDataSize -= releasedClusters * _bytesPerCluster;
}
_cookedRuns.CollapseRuns();
}
private static CookedDataRuns CookRuns(NtfsAttribute attribute)
{
CookedDataRuns result = new CookedDataRuns();
foreach (NonResidentAttributeRecord record in attribute.Records)
{
if (record.StartVcn != result.NextVirtualCluster)
{
throw new IOException("Invalid NTFS attribute - non-contiguous data runs");
}
result.Append(record.DataRuns, record);
}
return result;
}
private void InitializeData(long pos)
{
long initDataLen = PrimaryAttributeRecord.InitializedDataLength;
_file.MarkMftRecordDirty();
int clustersAllocated = 0;
while (initDataLen < pos)
{
long vcn = initDataLen / _bytesPerCluster;
if (initDataLen % _bytesPerCluster != 0 || pos - initDataLen < _bytesPerCluster)
{
int clusterOffset = (int)(initDataLen - (vcn * _bytesPerCluster));
int toClear = (int)Math.Min(_bytesPerCluster - clusterOffset, pos - initDataLen);
if (_activeStream.IsClusterStored(vcn))
{
_activeStream.ReadClusters(vcn, 1, _ioBuffer, 0);
Array.Clear(_ioBuffer, clusterOffset, toClear);
clustersAllocated += _activeStream.WriteClusters(vcn, 1, _ioBuffer, 0);
}
initDataLen += toClear;
}
else
{
int numClusters = (int)((pos / _bytesPerCluster) - vcn);
clustersAllocated -= _activeStream.ClearClusters(vcn, numClusters);
initDataLen += numClusters * _bytesPerCluster;
}
}
PrimaryAttributeRecord.InitializedDataLength = pos;
if ((_attribute.Flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0)
{
PrimaryAttributeRecord.CompressedDataSize += clustersAllocated * _bytesPerCluster;
}
}
private void Truncate(long value)
{
long endVcn = Utilities.Ceil(value, _bytesPerCluster);
// Release the clusters
_activeStream.TruncateToClusters(endVcn);
// First, remove any extents that are now redundant.
Dictionary<AttributeReference, AttributeRecord> extentCache = new Dictionary<AttributeReference, AttributeRecord>(_attribute.Extents);
foreach (var extent in extentCache)
{
if (extent.Value.StartVcn >= endVcn)
{
NonResidentAttributeRecord record = (NonResidentAttributeRecord)extent.Value;
_file.RemoveAttributeExtent(extent.Key);
_attribute.RemoveExtentCacheSafe(extent.Key);
}
}
PrimaryAttributeRecord.LastVcn = Math.Max(0, endVcn - 1);
PrimaryAttributeRecord.AllocatedLength = endVcn * _bytesPerCluster;
PrimaryAttributeRecord.DataLength = value;
PrimaryAttributeRecord.InitializedDataLength = Math.Min(PrimaryAttributeRecord.InitializedDataLength, value);
_file.MarkMftRecordDirty();
}
}
}
@@ -0,0 +1,401 @@
//
// 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.IO;
using System.Text;
internal sealed class NonResidentAttributeRecord : AttributeRecord
{
private const ushort DefaultCompressionUnitSize = 4;
private ulong _startingVCN;
private ulong _lastVCN;
private ushort _dataRunsOffset;
private ushort _compressionUnitSize;
private ulong _dataAllocatedSize;
private ulong _dataRealSize;
private ulong _initializedDataSize;
private ulong _compressedSize;
private List<DataRun> _dataRuns;
public NonResidentAttributeRecord(byte[] buffer, int offset, out int length)
{
Read(buffer, offset, out length);
}
public NonResidentAttributeRecord(AttributeType type, string name, ushort id, AttributeFlags flags, long firstCluster, ulong numClusters, uint bytesPerCluster)
: base(type, name, id, flags)
{
_nonResidentFlag = 1;
_dataRuns = new List<DataRun>();
_dataRuns.Add(new DataRun(firstCluster, (long)numClusters, false));
_lastVCN = numClusters - 1;
_dataAllocatedSize = bytesPerCluster * numClusters;
_dataRealSize = bytesPerCluster * numClusters;
_initializedDataSize = bytesPerCluster * numClusters;
if ((flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0)
{
_compressionUnitSize = DefaultCompressionUnitSize;
}
}
public NonResidentAttributeRecord(AttributeType type, string name, ushort id, AttributeFlags flags, long startVcn, List<DataRun> dataRuns)
: base(type, name, id, flags)
{
_nonResidentFlag = 1;
_dataRuns = dataRuns;
_startingVCN = (ulong)startVcn;
if ((flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0)
{
_compressionUnitSize = DefaultCompressionUnitSize;
}
if (dataRuns != null && dataRuns.Count != 0)
{
_lastVCN = _startingVCN;
foreach (var run in dataRuns)
{
_lastVCN += (ulong)run.RunLength;
}
_lastVCN -= 1;
}
}
/// <summary>
/// The amount of space occupied by the attribute (in bytes).
/// </summary>
public override long AllocatedLength
{
get { return (long)_dataAllocatedSize; }
set { _dataAllocatedSize = (ulong)value; }
}
/// <summary>
/// The amount of data in the attribute (in bytes).
/// </summary>
public override long DataLength
{
get { return (long)_dataRealSize; }
set { _dataRealSize = (ulong)value; }
}
/// <summary>
/// The amount of initialized data in the attribute (in bytes).
/// </summary>
public override long InitializedDataLength
{
get { return (long)_initializedDataSize; }
set { _initializedDataSize = (ulong)value; }
}
public long CompressedDataSize
{
get { return (long)_compressedSize; }
set { _compressedSize = (ulong)value; }
}
public override long StartVcn
{
get { return (long)_startingVCN; }
}
public long LastVcn
{
get { return (long)_lastVCN; }
set { _lastVCN = (ulong)value; }
}
/// <summary>
/// Gets or sets the size of a compression unit (in clusters).
/// </summary>
public int CompressionUnitSize
{
get { return 1 << _compressionUnitSize; }
set { _compressionUnitSize = (ushort)Utilities.Log2(value); }
}
public List<DataRun> DataRuns
{
get { return _dataRuns; }
}
public override int Size
{
get
{
byte nameLength = 0;
ushort nameOffset = (ushort)(((Flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0) ? 0x48 : 0x40);
if (Name != null)
{
nameLength = (byte)Name.Length;
}
ushort dataOffset = (ushort)Utilities.RoundUp(nameOffset + (nameLength * 2), 8);
// Write out data first, since we know where it goes...
int dataLen = 0;
foreach (var run in _dataRuns)
{
dataLen += run.Size;
}
dataLen++; // NULL terminator
return Utilities.RoundUp(dataOffset + dataLen, 8);
}
}
public void ReplaceRun(DataRun oldRun, DataRun newRun)
{
int idx = _dataRuns.IndexOf(oldRun);
if (idx < 0)
{
throw new ArgumentException("Attempt to replace non-existant run", "oldRun");
}
_dataRuns[idx] = newRun;
}
public int RemoveRun(DataRun run)
{
int idx = _dataRuns.IndexOf(run);
if (idx < 0)
{
throw new ArgumentException("Attempt to remove non-existant run", "run");
}
_dataRuns.RemoveAt(idx);
return idx;
}
public void InsertRun(DataRun existingRun, DataRun newRun)
{
int idx = _dataRuns.IndexOf(existingRun);
if (idx < 0)
{
throw new ArgumentException("Attempt to replace non-existant run", "existingRun");
}
_dataRuns.Insert(idx + 1, newRun);
}
public void InsertRun(int index, DataRun newRun)
{
_dataRuns.Insert(index, newRun);
}
public override Range<long, long>[] GetClusters()
{
var cookedRuns = _dataRuns;
long start = 0;
List<Range<long, long>> result = new List<Range<long, long>>(_dataRuns.Count);
foreach (var run in cookedRuns)
{
if (!run.IsSparse)
{
start += run.RunOffset;
result.Add(new Range<long, long>(start, run.RunLength));
}
}
return result.ToArray();
}
public override IBuffer GetReadOnlyDataBuffer(INtfsContext context)
{
return new NonResidentDataBuffer(context, this);
}
public override int Write(byte[] buffer, int offset)
{
ushort headerLength = 0x40;
if ((Flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0)
{
headerLength += 0x08;
}
byte nameLength = 0;
ushort nameOffset = headerLength;
if (Name != null)
{
nameLength = (byte)Name.Length;
}
ushort dataOffset = (ushort)Utilities.RoundUp(headerLength + (nameLength * 2), 8);
// Write out data first, since we know where it goes...
int dataLen = 0;
foreach (var run in _dataRuns)
{
dataLen += run.Write(buffer, offset + dataOffset + dataLen);
}
buffer[offset + dataOffset + dataLen] = 0; // NULL terminator
dataLen++;
int length = (int)Utilities.RoundUp(dataOffset + dataLen, 8);
Utilities.WriteBytesLittleEndian((uint)_type, buffer, offset + 0x00);
Utilities.WriteBytesLittleEndian(length, buffer, offset + 0x04);
buffer[offset + 0x08] = _nonResidentFlag;
buffer[offset + 0x09] = nameLength;
Utilities.WriteBytesLittleEndian(nameOffset, buffer, offset + 0x0A);
Utilities.WriteBytesLittleEndian((ushort)_flags, buffer, offset + 0x0C);
Utilities.WriteBytesLittleEndian(_attributeId, buffer, offset + 0x0E);
Utilities.WriteBytesLittleEndian(_startingVCN, buffer, offset + 0x10);
Utilities.WriteBytesLittleEndian(_lastVCN, buffer, offset + 0x18);
Utilities.WriteBytesLittleEndian(dataOffset, buffer, offset + 0x20);
Utilities.WriteBytesLittleEndian(_compressionUnitSize, buffer, offset + 0x22);
Utilities.WriteBytesLittleEndian((uint)0, buffer, offset + 0x24); // Padding
Utilities.WriteBytesLittleEndian(_dataAllocatedSize, buffer, offset + 0x28);
Utilities.WriteBytesLittleEndian(_dataRealSize, buffer, offset + 0x30);
Utilities.WriteBytesLittleEndian(_initializedDataSize, buffer, offset + 0x38);
if ((Flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0)
{
Utilities.WriteBytesLittleEndian(_compressedSize, buffer, offset + 0x40);
}
if (Name != null)
{
Array.Copy(Encoding.Unicode.GetBytes(Name), 0, buffer, offset + nameOffset, nameLength * 2);
}
return length;
}
public AttributeRecord Split(int suggestedSplitIdx)
{
int splitIdx;
if (suggestedSplitIdx <= 0 || suggestedSplitIdx >= _dataRuns.Count)
{
splitIdx = _dataRuns.Count / 2;
}
else
{
splitIdx = suggestedSplitIdx;
}
long splitVcn = (long)_startingVCN;
long splitLcn = 0;
for (int i = 0; i < splitIdx; ++i)
{
splitVcn += _dataRuns[i].RunLength;
splitLcn += _dataRuns[i].RunOffset;
}
List<DataRun> newRecordRuns = new List<DataRun>();
while (_dataRuns.Count > splitIdx)
{
DataRun run = _dataRuns[splitIdx];
_dataRuns.RemoveAt(splitIdx);
newRecordRuns.Add(run);
}
// Each extent has implicit start LCN=0, so have to make stored runs match reality.
// However, take care not to stomp on 'sparse' runs that may be at the start of the
// new extent (indicated by Zero run offset).
for (int i = 0; i < newRecordRuns.Count; ++i)
{
if (!newRecordRuns[i].IsSparse)
{
newRecordRuns[i].RunOffset += splitLcn;
break;
}
}
_lastVCN = (ulong)splitVcn - 1;
return new NonResidentAttributeRecord(_type, _name, 0, _flags, splitVcn, newRecordRuns);
}
public override void Dump(TextWriter writer, string indent)
{
base.Dump(writer, indent);
writer.WriteLine(indent + " Starting VCN: " + _startingVCN);
writer.WriteLine(indent + " Last VCN: " + _lastVCN);
writer.WriteLine(indent + " Comp Unit Size: " + _compressionUnitSize);
writer.WriteLine(indent + " Allocated Size: " + _dataAllocatedSize);
writer.WriteLine(indent + " Real Size: " + _dataRealSize);
writer.WriteLine(indent + " Init Data Size: " + _initializedDataSize);
if ((Flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0)
{
writer.WriteLine(indent + " Compressed Size: " + _compressedSize);
}
string runStr = string.Empty;
foreach (DataRun run in _dataRuns)
{
runStr += " " + run.ToString();
}
writer.WriteLine(indent + " Data Runs:" + runStr);
}
protected override void Read(byte[] buffer, int offset, out int length)
{
_dataRuns = null;
base.Read(buffer, offset, out length);
_startingVCN = Utilities.ToUInt64LittleEndian(buffer, offset + 0x10);
_lastVCN = Utilities.ToUInt64LittleEndian(buffer, offset + 0x18);
_dataRunsOffset = Utilities.ToUInt16LittleEndian(buffer, offset + 0x20);
_compressionUnitSize = Utilities.ToUInt16LittleEndian(buffer, offset + 0x22);
_dataAllocatedSize = Utilities.ToUInt64LittleEndian(buffer, offset + 0x28);
_dataRealSize = Utilities.ToUInt64LittleEndian(buffer, offset + 0x30);
_initializedDataSize = Utilities.ToUInt64LittleEndian(buffer, offset + 0x38);
if ((Flags & (AttributeFlags.Compressed | AttributeFlags.Sparse)) != 0 && _dataRunsOffset > 0x40)
{
_compressedSize = Utilities.ToUInt64LittleEndian(buffer, offset + 0x40);
}
_dataRuns = new List<DataRun>();
int pos = _dataRunsOffset;
while (pos < length)
{
DataRun run = new DataRun();
int len = run.Read(buffer, offset + pos);
// Length 1 means there was only a header byte (i.e. terminator)
if (len == 1)
{
break;
}
_dataRuns.Add(run);
pos += len;
}
}
}
}
+169
View File
@@ -0,0 +1,169 @@
//
// 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.IO;
using DiscUtils.Compression;
internal class NonResidentDataBuffer : DiscUtils.Buffer, IMappedBuffer
{
protected INtfsContext _context;
protected CookedDataRuns _cookedRuns;
protected long _bytesPerCluster;
protected RawClusterStream _rawStream;
protected ClusterStream _activeStream;
protected byte[] _ioBuffer;
public NonResidentDataBuffer(INtfsContext context, NonResidentAttributeRecord record)
: this(context, new CookedDataRuns(record.DataRuns, record), false)
{
}
public NonResidentDataBuffer(INtfsContext context, CookedDataRuns cookedRuns, bool isMft)
{
_context = context;
_cookedRuns = cookedRuns;
_rawStream = new RawClusterStream(_context, _cookedRuns, isMft);
_activeStream = _rawStream;
_bytesPerCluster = _context.BiosParameterBlock.BytesPerCluster;
_ioBuffer = new byte[_bytesPerCluster];
}
public override bool CanRead
{
get { return _context.RawStream.CanRead; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Capacity
{
get { return VirtualClusterCount * _bytesPerCluster; }
}
public long VirtualClusterCount
{
get { return _cookedRuns.NextVirtualCluster; }
}
public override IEnumerable<StreamExtent> Extents
{
get
{
List<StreamExtent> extents = new List<StreamExtent>();
foreach (var range in _activeStream.StoredClusters)
{
extents.Add(new StreamExtent(range.Offset * _bytesPerCluster, range.Count * _bytesPerCluster));
}
return StreamExtent.Intersect(extents, new StreamExtent(0, Capacity));
}
}
public override IEnumerable<StreamExtent> GetExtentsInRange(long start, long count)
{
return StreamExtent.Intersect(Extents, new StreamExtent(start, count));
}
public long MapPosition(long pos)
{
long vcn = pos / _bytesPerCluster;
int dataRunIdx = _cookedRuns.FindDataRun(vcn, 0);
if (_cookedRuns[dataRunIdx].IsSparse)
{
return -1;
}
else
{
return (_cookedRuns[dataRunIdx].StartLcn * _bytesPerCluster) + (pos - (_cookedRuns[dataRunIdx].StartVcn * _bytesPerCluster));
}
}
public override int Read(long pos, byte[] buffer, int offset, int count)
{
if (!CanRead)
{
throw new IOException("Attempt to read from file not opened for read");
}
Utilities.AssertBufferParameters(buffer, offset, count);
// Limit read to length of attribute
int totalToRead = (int)Math.Min(count, Capacity - pos);
if (totalToRead <= 0)
{
return 0;
}
long focusPos = pos;
while (focusPos < pos + totalToRead)
{
long vcn = focusPos / _bytesPerCluster;
long remaining = (pos + totalToRead) - focusPos;
long clusterOffset = focusPos - (vcn * _bytesPerCluster);
if (vcn * _bytesPerCluster != focusPos || remaining < _bytesPerCluster)
{
// Unaligned or short read
_activeStream.ReadClusters(vcn, 1, _ioBuffer, 0);
int toRead = (int)Math.Min(remaining, _bytesPerCluster - clusterOffset);
Array.Copy(_ioBuffer, clusterOffset, buffer, offset + (focusPos - pos), toRead);
focusPos += toRead;
}
else
{
// Aligned, full cluster reads...
int fullClusters = (int)(remaining / _bytesPerCluster);
_activeStream.ReadClusters(vcn, fullClusters, buffer, (int)(offset + (focusPos - pos)));
focusPos += fullClusters * _bytesPerCluster;
}
}
return totalToRead;
}
public override void Write(long pos, byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override void SetCapacity(long value)
{
throw new NotSupportedException();
}
}
}
+414
View File
@@ -0,0 +1,414 @@
//
// 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;
internal class NtfsAttribute : IDiagnosticTraceable
{
protected File _file;
protected FileRecordReference _containingFile;
protected AttributeRecord _primaryRecord;
protected Dictionary<AttributeReference, AttributeRecord> _extents;
private IBuffer _cachedRawBuffer;
protected NtfsAttribute(File file, FileRecordReference containingFile, AttributeRecord record)
{
_file = file;
_containingFile = containingFile;
_primaryRecord = record;
_extents = new Dictionary<AttributeReference, AttributeRecord>();
_extents.Add(new AttributeReference(containingFile, record.AttributeId), _primaryRecord);
}
public AttributeReference Reference
{
get
{
return new AttributeReference(_containingFile, _primaryRecord.AttributeId);
}
}
public AttributeType Type
{
get { return _primaryRecord.AttributeType; }
}
public string Name
{
get { return _primaryRecord.Name; }
}
public AttributeFlags Flags
{
get
{
return _primaryRecord.Flags;
}
set
{
_primaryRecord.Flags = value;
_cachedRawBuffer = null;
}
}
public ushort Id
{
get { return _primaryRecord.AttributeId; }
}
public long Length
{
get { return _primaryRecord.DataLength; }
}
public AttributeRecord PrimaryRecord
{
get
{
return _primaryRecord;
}
}
public int CompressionUnitSize
{
get
{
NonResidentAttributeRecord firstExtent = FirstExtent as NonResidentAttributeRecord;
if (firstExtent == null)
{
return 0;
}
else
{
return firstExtent.CompressionUnitSize;
}
}
set
{
NonResidentAttributeRecord firstExtent = FirstExtent as NonResidentAttributeRecord;
if (firstExtent != null)
{
firstExtent.CompressionUnitSize = value;
}
}
}
public long CompressedDataSize
{
get
{
NonResidentAttributeRecord firstExtent = FirstExtent as NonResidentAttributeRecord;
if (firstExtent == null)
{
return FirstExtent.AllocatedLength;
}
else
{
return firstExtent.CompressedDataSize;
}
}
set
{
NonResidentAttributeRecord firstExtent = FirstExtent as NonResidentAttributeRecord;
if (firstExtent != null)
{
firstExtent.CompressedDataSize = value;
}
}
}
public List<AttributeRecord> Records
{
get
{
List<AttributeRecord> records = new List<AttributeRecord>(_extents.Values);
records.Sort(AttributeRecord.CompareStartVcns);
return records;
}
}
public IBuffer RawBuffer
{
get
{
if (_cachedRawBuffer == null)
{
if (_primaryRecord.IsNonResident)
{
_cachedRawBuffer = new NonResidentAttributeBuffer(_file, this);
}
else
{
_cachedRawBuffer = ((ResidentAttributeRecord)_primaryRecord).DataBuffer;
}
}
return _cachedRawBuffer;
}
}
public IDictionary<AttributeReference, AttributeRecord> Extents
{
get { return _extents; }
}
public AttributeRecord LastExtent
{
get
{
AttributeRecord last = null;
if (_extents != null)
{
long lastVcn = 0;
foreach (var extent in _extents)
{
NonResidentAttributeRecord nonResident = extent.Value as NonResidentAttributeRecord;
if (nonResident == null)
{
// Resident attribute, so there can only be one...
return extent.Value;
}
if (nonResident.LastVcn >= lastVcn)
{
last = extent.Value;
lastVcn = nonResident.LastVcn;
}
}
}
return last;
}
}
public AttributeRecord FirstExtent
{
get
{
if (_extents != null)
{
foreach (var extent in _extents)
{
NonResidentAttributeRecord nonResident = extent.Value as NonResidentAttributeRecord;
if (nonResident == null)
{
// Resident attribute, so there can only be one...
return extent.Value;
}
else if (nonResident.StartVcn == 0)
{
return extent.Value;
}
}
}
throw new InvalidDataException("Attribute with no initial extent");
}
}
public bool IsNonResident
{
get { return _primaryRecord.IsNonResident; }
}
protected string AttributeTypeName
{
get
{
switch (_primaryRecord.AttributeType)
{
case AttributeType.StandardInformation:
return "STANDARD INFORMATION";
case AttributeType.FileName:
return "FILE NAME";
case AttributeType.SecurityDescriptor:
return "SECURITY DESCRIPTOR";
case AttributeType.Data:
return "DATA";
case AttributeType.Bitmap:
return "BITMAP";
case AttributeType.VolumeName:
return "VOLUME NAME";
case AttributeType.VolumeInformation:
return "VOLUME INFORMATION";
case AttributeType.IndexRoot:
return "INDEX ROOT";
case AttributeType.IndexAllocation:
return "INDEX ALLOCATION";
case AttributeType.ObjectId:
return "OBJECT ID";
case AttributeType.ReparsePoint:
return "REPARSE POINT";
case AttributeType.AttributeList:
return "ATTRIBUTE LIST";
default:
return "UNKNOWN";
}
}
}
public static NtfsAttribute FromRecord(File file, FileRecordReference recordFile, AttributeRecord record)
{
switch (record.AttributeType)
{
case AttributeType.StandardInformation:
return new StructuredNtfsAttribute<StandardInformation>(file, recordFile, record);
case AttributeType.FileName:
return new StructuredNtfsAttribute<FileNameRecord>(file, recordFile, record);
case AttributeType.SecurityDescriptor:
return new StructuredNtfsAttribute<SecurityDescriptor>(file, recordFile, record);
case AttributeType.Data:
return new NtfsAttribute(file, recordFile, record);
case AttributeType.Bitmap:
return new NtfsAttribute(file, recordFile, record);
case AttributeType.VolumeName:
return new StructuredNtfsAttribute<VolumeName>(file, recordFile, record);
case AttributeType.VolumeInformation:
return new StructuredNtfsAttribute<VolumeInformation>(file, recordFile, record);
case AttributeType.IndexRoot:
return new NtfsAttribute(file, recordFile, record);
case AttributeType.IndexAllocation:
return new NtfsAttribute(file, recordFile, record);
case AttributeType.ObjectId:
return new StructuredNtfsAttribute<ObjectId>(file, recordFile, record);
case AttributeType.ReparsePoint:
return new StructuredNtfsAttribute<ReparsePointRecord>(file, recordFile, record);
case AttributeType.AttributeList:
return new StructuredNtfsAttribute<AttributeList>(file, recordFile, record);
default:
return new NtfsAttribute(file, recordFile, record);
}
}
public void SetExtent(FileRecordReference containingFile, AttributeRecord record)
{
_cachedRawBuffer = null;
_containingFile = containingFile;
_primaryRecord = record;
_extents.Clear();
_extents.Add(new AttributeReference(containingFile, record.AttributeId), record);
}
public void AddExtent(FileRecordReference containingFile, AttributeRecord record)
{
_cachedRawBuffer = null;
_extents.Add(new AttributeReference(containingFile, record.AttributeId), record);
}
public void RemoveExtentCacheSafe(AttributeReference reference)
{
_extents.Remove(reference);
}
public bool ReplaceExtent(AttributeReference oldRef, AttributeReference newRef, AttributeRecord record)
{
_cachedRawBuffer = null;
if (!_extents.Remove(oldRef))
{
return false;
}
else
{
if (oldRef.Equals(Reference) || _extents.Count == 0)
{
_primaryRecord = record;
_containingFile = newRef.File;
}
_extents.Add(newRef, record);
return true;
}
}
public Range<long, long>[] GetClusters()
{
List<Range<long, long>> result = new List<Range<long, long>>();
foreach (var extent in _extents)
{
result.AddRange(extent.Value.GetClusters());
}
return result.ToArray();
}
public virtual void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + AttributeTypeName + " ATTRIBUTE (" + (Name == null ? "No Name" : Name) + ")");
writer.WriteLine(indent + " Length: " + _primaryRecord.DataLength + " bytes");
if (_primaryRecord.DataLength == 0)
{
writer.WriteLine(indent + " Data: <none>");
}
else
{
try
{
using (Stream s = Open(FileAccess.Read))
{
string hex = string.Empty;
byte[] buffer = new byte[32];
int numBytes = s.Read(buffer, 0, buffer.Length);
for (int i = 0; i < numBytes; ++i)
{
hex = hex + string.Format(CultureInfo.InvariantCulture, " {0:X2}", buffer[i]);
}
writer.WriteLine(indent + " Data: " + hex + ((numBytes < s.Length) ? "..." : string.Empty));
}
}
catch
{
writer.WriteLine(indent + " Data: <can't read>");
}
}
_primaryRecord.Dump(writer, indent + " ");
}
internal SparseStream Open(FileAccess access)
{
return new BufferStream(GetDataBuffer(), access);
}
internal IMappedBuffer GetDataBuffer()
{
return new NtfsAttributeBuffer(_file, this);
}
internal long OffsetToAbsolutePos(long offset)
{
return GetDataBuffer().MapPosition(offset);
}
}
}
+198
View File
@@ -0,0 +1,198 @@
//
// 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.IO;
internal class NtfsAttributeBuffer : DiscUtils.Buffer, IMappedBuffer
{
private File _file;
private NtfsAttribute _attribute;
public NtfsAttributeBuffer(File file, NtfsAttribute attribute)
{
_file = file;
_attribute = attribute;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return _file.Context.RawStream.CanWrite; }
}
public override long Capacity
{
get
{
return _attribute.PrimaryRecord.DataLength;
}
}
public long MapPosition(long pos)
{
if (_attribute.IsNonResident)
{
return ((IMappedBuffer)_attribute.RawBuffer).MapPosition(pos);
}
else
{
AttributeReference attrRef = new AttributeReference(_file.MftReference, _attribute.PrimaryRecord.AttributeId);
ResidentAttributeRecord attrRecord = (ResidentAttributeRecord)_file.GetAttribute(attrRef).PrimaryRecord;
long attrStart = _file.GetAttributeOffset(attrRef);
long mftPos = attrStart + attrRecord.DataOffset + pos;
return _file.Context.GetFileByIndex(MasterFileTable.MftIndex).GetAttribute(AttributeType.Data, null).OffsetToAbsolutePos(mftPos);
}
}
public override int Read(long pos, byte[] buffer, int offset, int count)
{
var record = _attribute.PrimaryRecord;
if (!CanRead)
{
throw new IOException("Attempt to read from file not opened for read");
}
Utilities.AssertBufferParameters(buffer, offset, count);
if (pos >= Capacity)
{
return 0;
}
// Limit read to length of attribute
int totalToRead = (int)Math.Min(count, Capacity - pos);
int toRead = totalToRead;
// Handle uninitialized bytes at end of attribute
if (pos + totalToRead > record.InitializedDataLength)
{
if (pos >= record.InitializedDataLength)
{
// We're just reading zero bytes from the uninitialized area
Array.Clear(buffer, offset, totalToRead);
pos += totalToRead;
return totalToRead;
}
else
{
// Partial read of uninitialized area
Array.Clear(buffer, offset + (int)(record.InitializedDataLength - pos), (int)((pos + toRead) - record.InitializedDataLength));
toRead = (int)(record.InitializedDataLength - pos);
}
}
int numRead = 0;
while (numRead < toRead)
{
IBuffer extentBuffer = _attribute.RawBuffer;
int justRead = extentBuffer.Read(pos + numRead, buffer, offset + numRead, toRead - numRead);
if (justRead == 0)
{
break;
}
numRead += justRead;
}
return totalToRead;
}
public override void SetCapacity(long value)
{
if (!CanWrite)
{
throw new IOException("Attempt to change length of file not opened for write");
}
if (value == Capacity)
{
return;
}
_attribute.RawBuffer.SetCapacity(value);
_file.MarkMftRecordDirty();
}
public override void Write(long pos, byte[] buffer, int offset, int count)
{
var record = _attribute.PrimaryRecord;
if (!CanWrite)
{
throw new IOException("Attempt to write to file not opened for write");
}
Utilities.AssertBufferParameters(buffer, offset, count);
if (count == 0)
{
return;
}
_attribute.RawBuffer.Write(pos, buffer, offset, count);
if (!record.IsNonResident)
{
_file.MarkMftRecordDirty();
}
}
public override void Clear(long pos, int count)
{
var record = _attribute.PrimaryRecord;
if (!CanWrite)
{
throw new IOException("Attempt to write to file not opened for write");
}
if (count == 0)
{
return;
}
_attribute.RawBuffer.Clear(pos, count);
if (!record.IsNonResident)
{
_file.MarkMftRecordDirty();
}
}
public override IEnumerable<StreamExtent> GetExtentsInRange(long start, long count)
{
return StreamExtent.Intersect(_attribute.RawBuffer.GetExtentsInRange(start, count), new StreamExtent(0, Capacity));
}
}
}
+261
View File
@@ -0,0 +1,261 @@
//
// 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.IO;
internal delegate File GetFileByIndexFn(long index);
internal delegate File GetFileByRefFn(FileRecordReference reference);
internal delegate Directory GetDirectoryByIndexFn(long index);
internal delegate Directory GetDirectoryByRefFn(FileRecordReference reference);
internal delegate File AllocateFileFn(FileRecordFlags flags);
internal delegate void ForgetFileFn(File file);
internal interface INtfsContext
{
Stream RawStream
{
get;
}
AttributeDefinitions AttributeDefinitions
{
get;
}
UpperCase UpperCase
{
get;
}
BiosParameterBlock BiosParameterBlock
{
get;
}
MasterFileTable Mft
{
get;
}
ClusterBitmap ClusterBitmap
{
get;
}
SecurityDescriptors SecurityDescriptors
{
get;
}
ObjectIds ObjectIds
{
get;
}
ReparsePoints ReparsePoints
{
get;
}
Quotas Quotas
{
get;
}
NtfsOptions Options
{
get;
}
GetFileByIndexFn GetFileByIndex
{
get;
}
GetFileByRefFn GetFileByRef
{
get;
}
GetDirectoryByIndexFn GetDirectoryByIndex
{
get;
}
GetDirectoryByRefFn GetDirectoryByRef
{
get;
}
AllocateFileFn AllocateFile
{
get;
}
ForgetFileFn ForgetFile
{
get;
}
bool ReadOnly
{
get;
}
}
internal sealed class NtfsContext : INtfsContext
{
private Stream _rawStream;
private AttributeDefinitions _attrDefs;
private UpperCase _upperCase;
private BiosParameterBlock _bpb;
private MasterFileTable _mft;
private ClusterBitmap _bitmap;
private SecurityDescriptors _securityDescriptors;
private ObjectIds _objectIds;
private ReparsePoints _reparsePoints;
private Quotas _quotas;
private NtfsOptions _options;
private GetFileByIndexFn _getFileByIndexFn;
private GetFileByRefFn _getFileByRefFn;
private GetDirectoryByIndexFn _getDirByIndexFn;
private GetDirectoryByRefFn _getDirByRefFn;
private AllocateFileFn _allocateFileFn;
private ForgetFileFn _forgetFileFn;
private bool _readOnly;
public Stream RawStream
{
get { return _rawStream; }
set { _rawStream = value; }
}
public AttributeDefinitions AttributeDefinitions
{
get { return _attrDefs; }
set { _attrDefs = value; }
}
public UpperCase UpperCase
{
get { return _upperCase; }
set { _upperCase = value; }
}
public BiosParameterBlock BiosParameterBlock
{
get { return _bpb; }
set { _bpb = value; }
}
public MasterFileTable Mft
{
get { return _mft; }
set { _mft = value; }
}
public ClusterBitmap ClusterBitmap
{
get { return _bitmap; }
set { _bitmap = value; }
}
public SecurityDescriptors SecurityDescriptors
{
get { return _securityDescriptors; }
set { _securityDescriptors = value; }
}
public ObjectIds ObjectIds
{
get { return _objectIds; }
set { _objectIds = value; }
}
public ReparsePoints ReparsePoints
{
get { return _reparsePoints; }
set { _reparsePoints = value; }
}
public Quotas Quotas
{
get { return _quotas; }
set { _quotas = value; }
}
public NtfsOptions Options
{
get { return _options; }
set { _options = value; }
}
public GetFileByIndexFn GetFileByIndex
{
get { return _getFileByIndexFn; }
set { _getFileByIndexFn = value; }
}
public GetFileByRefFn GetFileByRef
{
get { return _getFileByRefFn; }
set { _getFileByRefFn = value; }
}
public GetDirectoryByIndexFn GetDirectoryByIndex
{
get { return _getDirByIndexFn; }
set { _getDirByIndexFn = value; }
}
public GetDirectoryByRefFn GetDirectoryByRef
{
get { return _getDirByRefFn; }
set { _getDirByRefFn = value; }
}
public AllocateFileFn AllocateFile
{
get { return _allocateFileFn; }
set { _allocateFileFn = value; }
}
public ForgetFileFn ForgetFile
{
get { return _forgetFileFn; }
set { _forgetFileFn = value; }
}
public bool ReadOnly
{
get { return _readOnly; }
set { _readOnly = value; }
}
}
}
+224
View File
@@ -0,0 +1,224 @@
//
// 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.IO;
internal sealed class NtfsFileStream : SparseStream
{
private DirectoryEntry _entry;
private File _file;
private SparseStream _baseStream;
private bool _isDirty;
public NtfsFileStream(NtfsFileSystem fileSystem, DirectoryEntry entry, AttributeType attrType, string attrName, FileAccess access)
{
_entry = entry;
_file = fileSystem.GetFile(entry.Reference);
_baseStream = _file.OpenStream(attrType, attrName, access);
}
public override bool CanRead
{
get
{
AssertOpen();
return _baseStream.CanRead;
}
}
public override bool CanSeek
{
get
{
AssertOpen();
return _baseStream.CanSeek;
}
}
public override bool CanWrite
{
get
{
AssertOpen();
return _baseStream.CanWrite;
}
}
public override long Length
{
get
{
AssertOpen();
return _baseStream.Length;
}
}
public override long Position
{
get
{
AssertOpen();
return _baseStream.Position;
}
set
{
AssertOpen();
using (new NtfsTransaction())
{
_baseStream.Position = value;
}
}
}
public override IEnumerable<StreamExtent> Extents
{
get
{
AssertOpen();
return _baseStream.Extents;
}
}
public override void Close()
{
if (_baseStream == null)
{
return;
}
using (new NtfsTransaction())
{
base.Close();
_baseStream.Close();
UpdateMetadata();
_baseStream = null;
}
}
public override void Flush()
{
AssertOpen();
using (new NtfsTransaction())
{
_baseStream.Flush();
UpdateMetadata();
}
}
public override int Read(byte[] buffer, int offset, int count)
{
AssertOpen();
Utilities.AssertBufferParameters(buffer, offset, count);
using (new NtfsTransaction())
{
return _baseStream.Read(buffer, offset, count);
}
}
public override long Seek(long offset, SeekOrigin origin)
{
AssertOpen();
using (new NtfsTransaction())
{
return _baseStream.Seek(offset, origin);
}
}
public override void SetLength(long value)
{
AssertOpen();
using (new NtfsTransaction())
{
if (value != Length)
{
_isDirty = true;
_baseStream.SetLength(value);
}
}
}
public override void Write(byte[] buffer, int offset, int count)
{
AssertOpen();
Utilities.AssertBufferParameters(buffer, offset, count);
using (new NtfsTransaction())
{
_isDirty = true;
_baseStream.Write(buffer, offset, count);
}
}
public override void Clear(int count)
{
AssertOpen();
using (new NtfsTransaction())
{
_isDirty = true;
_baseStream.Clear(count);
}
}
private void UpdateMetadata()
{
if (!_file.Context.ReadOnly)
{
// Update the standard information attribute - so it reflects the actual file state
if (_isDirty)
{
_file.Modified();
}
else
{
_file.Accessed();
}
// Update the directory entry used to open the file, so it's accurate
_entry.UpdateFrom(_file);
// Write attribute changes back to the Master File Table
_file.UpdateRecordInMft();
_isDirty = false;
}
}
private void AssertOpen()
{
if (_baseStream == null)
{
throw new ObjectDisposedException(_entry.Details.FileName, "Attempt to use closed stream");
}
}
}
}
File diff suppressed because it is too large Load Diff
+618
View File
@@ -0,0 +1,618 @@
//
// 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;
using System.Runtime.Serialization;
using System.Text;
/// <summary>
/// Class that checks NTFS file system integrity.
/// </summary>
/// <remarks>Poor relation of chkdsk/fsck.</remarks>
public sealed class NtfsFileSystemChecker : DiscFileSystemChecker
{
private Stream _target;
private NtfsContext _context;
private TextWriter _report;
private ReportLevels _reportLevels;
private ReportLevels _levelsDetected;
private ReportLevels _levelsConsideredFail = ReportLevels.Errors;
/// <summary>
/// Initializes a new instance of the NtfsFileSystemChecker class.
/// </summary>
/// <param name="diskData">The file system to check.</param>
public NtfsFileSystemChecker(Stream diskData)
{
SnapshotStream protectiveStream = new SnapshotStream(diskData, Ownership.None);
protectiveStream.Snapshot();
protectiveStream.Freeze();
_target = protectiveStream;
}
/// <summary>
/// Checks the integrity of an NTFS file system held in a stream.
/// </summary>
/// <param name="reportOutput">A report on issues found.</param>
/// <param name="levels">The amount of detail to report.</param>
/// <returns><c>true</c> if the file system appears valid, else <c>false</c>.</returns>
public override bool Check(TextWriter reportOutput, ReportLevels levels)
{
_context = new NtfsContext();
_context.RawStream = _target;
_context.Options = new NtfsOptions();
_report = reportOutput;
_reportLevels = levels;
_levelsDetected = ReportLevels.None;
try
{
DoCheck();
}
catch (AbortException ae)
{
ReportError("File system check aborted: " + ae.ToString());
return false;
}
catch (Exception e)
{
ReportError("File system check aborted with exception: " + e.ToString());
return false;
}
return (_levelsDetected & _levelsConsideredFail) == 0;
}
/// <summary>
/// Gets an object that can convert between clusters and files.
/// </summary>
/// <returns>The cluster map.</returns>
public ClusterMap BuildClusterMap()
{
_context = new NtfsContext();
_context.RawStream = _target;
_context.Options = new NtfsOptions();
_context.RawStream.Position = 0;
byte[] bytes = Utilities.ReadFully(_context.RawStream, 512);
_context.BiosParameterBlock = BiosParameterBlock.FromBytes(bytes, 0);
_context.Mft = new MasterFileTable(_context);
File mftFile = new File(_context, _context.Mft.GetBootstrapRecord());
_context.Mft.Initialize(mftFile);
return _context.Mft.GetClusterMap();
}
private static void Abort()
{
throw new AbortException();
}
private void DoCheck()
{
_context.RawStream.Position = 0;
byte[] bytes = Utilities.ReadFully(_context.RawStream, 512);
_context.BiosParameterBlock = BiosParameterBlock.FromBytes(bytes, 0);
//-----------------------------------------------------------------------
// MASTER FILE TABLE
//
// Bootstrap the Master File Table
_context.Mft = new MasterFileTable(_context);
File mftFile = new File(_context, _context.Mft.GetBootstrapRecord());
// Verify basic MFT records before initializing the Master File Table
PreVerifyMft(mftFile);
_context.Mft.Initialize(mftFile);
// Now the MFT is up and running, do more detailed analysis of it's contents - double-accounted clusters, etc
VerifyMft();
_context.Mft.Dump(_report, "INFO: ");
//-----------------------------------------------------------------------
// INDEXES
//
// Need UpperCase in order to verify some indexes (i.e. directories).
File ucFile = new File(_context, _context.Mft.GetRecord(MasterFileTable.UpCaseIndex, false));
_context.UpperCase = new UpperCase(ucFile);
SelfCheckIndexes();
//-----------------------------------------------------------------------
// DIRECTORIES
//
VerifyDirectories();
//-----------------------------------------------------------------------
// WELL KNOWN FILES
//
VerifyWellKnownFilesExist();
//-----------------------------------------------------------------------
// OBJECT IDS
//
VerifyObjectIds();
//-----------------------------------------------------------------------
// FINISHED
//
// Temporary...
using (NtfsFileSystem fs = new NtfsFileSystem(_context.RawStream))
{
if ((_reportLevels & ReportLevels.Information) != 0)
{
ReportDump(fs);
}
}
}
private void VerifyWellKnownFilesExist()
{
Directory rootDir = new Directory(_context, _context.Mft.GetRecord(MasterFileTable.RootDirIndex, false));
DirectoryEntry extendDirEntry = rootDir.GetEntryByName("$Extend");
if (extendDirEntry == null)
{
ReportError("$Extend does not exist in root directory");
Abort();
}
Directory extendDir = new Directory(_context, _context.Mft.GetRecord(extendDirEntry.Reference));
DirectoryEntry objIdDirEntry = extendDir.GetEntryByName("$ObjId");
if (objIdDirEntry == null)
{
ReportError("$ObjId does not exist in $Extend directory");
Abort();
}
// Stash ObjectIds
_context.ObjectIds = new ObjectIds(new File(_context, _context.Mft.GetRecord(objIdDirEntry.Reference)));
DirectoryEntry sysVolInfDirEntry = rootDir.GetEntryByName("System Volume Information");
if (sysVolInfDirEntry == null)
{
ReportError("'System Volume Information' does not exist in root directory");
Abort();
}
////Directory sysVolInfDir = new Directory(_context, _context.Mft.GetRecord(sysVolInfDirEntry.Reference));
}
private void VerifyObjectIds()
{
foreach (FileRecord fr in _context.Mft.Records)
{
if (fr.BaseFile.Value != 0)
{
File f = new File(_context, fr);
foreach (var stream in f.AllStreams)
{
if (stream.AttributeType == AttributeType.ObjectId)
{
ObjectId objId = stream.GetContent<ObjectId>();
ObjectIdRecord objIdRec;
if (!_context.ObjectIds.TryGetValue(objId.Id, out objIdRec))
{
ReportError("ObjectId {0} for file {1} is not indexed", objId.Id, f.BestName);
}
else if (objIdRec.MftReference != f.MftReference)
{
ReportError("ObjectId {0} for file {1} points to {2}", objId.Id, f.BestName, objIdRec.MftReference);
}
}
}
}
}
foreach (var objIdRec in _context.ObjectIds.All)
{
if (_context.Mft.GetRecord(objIdRec.Value.MftReference) == null)
{
ReportError("ObjectId {0} refers to non-existant file {1}", objIdRec.Key, objIdRec.Value.MftReference);
}
}
}
private void VerifyDirectories()
{
foreach (FileRecord fr in _context.Mft.Records)
{
if (fr.BaseFile.Value != 0)
{
continue;
}
File f = new File(_context, fr);
foreach (var stream in f.AllStreams)
{
if (stream.AttributeType == AttributeType.IndexRoot && stream.Name == "$I30")
{
IndexView<FileNameRecord, FileRecordReference> dir = new IndexView<FileNameRecord, FileRecordReference>(f.GetIndex("$I30"));
foreach (var entry in dir.Entries)
{
FileRecord refFile = _context.Mft.GetRecord(entry.Value);
// Make sure each referenced file actually exists...
if (refFile == null)
{
ReportError("Directory {0} references non-existent file {1}", f, entry.Key);
}
File referencedFile = new File(_context, refFile);
StandardInformation si = referencedFile.StandardInformation;
if (si.CreationTime != entry.Key.CreationTime || si.MftChangedTime != entry.Key.MftChangedTime
|| si.ModificationTime != entry.Key.ModificationTime)
{
ReportInfo("Directory entry {0} in {1} is out of date", entry.Key, f);
}
}
}
}
}
}
private void SelfCheckIndexes()
{
foreach (FileRecord fr in _context.Mft.Records)
{
File f = new File(_context, fr);
foreach (var stream in f.AllStreams)
{
if (stream.AttributeType == AttributeType.IndexRoot)
{
SelfCheckIndex(f, stream.Name);
}
}
}
}
private void SelfCheckIndex(File file, string name)
{
ReportInfo("About to self-check index {0} in file {1} (MFT:{2})", name, file.BestName, file.IndexInMft);
IndexRoot root = file.GetStream(AttributeType.IndexRoot, name).GetContent<IndexRoot>();
byte[] rootBuffer;
using (Stream s = file.OpenStream(AttributeType.IndexRoot, name, FileAccess.Read))
{
rootBuffer = Utilities.ReadFully(s, (int)s.Length);
}
Bitmap indexBitmap = null;
if (file.GetStream(AttributeType.Bitmap, name) != null)
{
indexBitmap = new Bitmap(file.OpenStream(AttributeType.Bitmap, name, FileAccess.Read), long.MaxValue);
}
if (!SelfCheckIndexNode(rootBuffer, IndexRoot.HeaderOffset, indexBitmap, root, file.BestName, name))
{
ReportError("Index {0} in file {1} (MFT:{2}) has corrupt IndexRoot attribute", name, file.BestName, file.IndexInMft);
}
else
{
ReportInfo("Self-check of index {0} in file {1} (MFT:{2}) complete", name, file.BestName, file.IndexInMft);
}
}
private bool SelfCheckIndexNode(byte[] buffer, int offset, Bitmap bitmap, IndexRoot root, string fileName, string indexName)
{
bool ok = true;
IndexHeader header = new IndexHeader(buffer, offset);
IndexEntry lastEntry = null;
IComparer<byte[]> collator = root.GetCollator(_context.UpperCase);
int pos = (int)header.OffsetToFirstEntry;
while (pos < header.TotalSizeOfEntries)
{
IndexEntry entry = new IndexEntry(indexName == "$I30");
entry.Read(buffer, offset + pos);
pos += entry.Size;
if ((entry.Flags & IndexEntryFlags.Node) != 0)
{
long bitmapIdx = entry.ChildrenVirtualCluster / Utilities.Ceil(root.IndexAllocationSize, _context.BiosParameterBlock.SectorsPerCluster * _context.BiosParameterBlock.BytesPerSector);
if (!bitmap.IsPresent(bitmapIdx))
{
ReportError("Index entry {0} is non-leaf, but child vcn {1} is not in bitmap at index {2}", Index.EntryAsString(entry, fileName, indexName), entry.ChildrenVirtualCluster, bitmapIdx);
}
}
if ((entry.Flags & IndexEntryFlags.End) != 0)
{
if (pos != header.TotalSizeOfEntries)
{
ReportError("Found END index entry {0}, but not at end of node", Index.EntryAsString(entry, fileName, indexName));
ok = false;
}
}
if (lastEntry != null && collator.Compare(lastEntry.KeyBuffer, entry.KeyBuffer) >= 0)
{
ReportError("Found entries out of order {0} was before {1}", Index.EntryAsString(lastEntry, fileName, indexName), Index.EntryAsString(entry, fileName, indexName));
ok = false;
}
lastEntry = entry;
}
return ok;
}
private void PreVerifyMft(File file)
{
int recordLength = _context.BiosParameterBlock.MftRecordSize;
int bytesPerSector = _context.BiosParameterBlock.BytesPerSector;
// Check out the MFT's clusters
foreach (var range in file.GetAttribute(AttributeType.Data, null).GetClusters())
{
if (!VerifyClusterRange(range))
{
ReportError("Corrupt cluster range in MFT data attribute {0}", range.ToString());
Abort();
}
}
foreach (var range in file.GetAttribute(AttributeType.Bitmap, null).GetClusters())
{
if (!VerifyClusterRange(range))
{
ReportError("Corrupt cluster range in MFT bitmap attribute {0}", range.ToString());
Abort();
}
}
using (Stream mftStream = file.OpenStream(AttributeType.Data, null, FileAccess.Read))
using (Stream bitmapStream = file.OpenStream(AttributeType.Bitmap, null, FileAccess.Read))
{
Bitmap bitmap = new Bitmap(bitmapStream, long.MaxValue);
long index = 0;
while (mftStream.Position < mftStream.Length)
{
byte[] recordData = Utilities.ReadFully(mftStream, recordLength);
string magic = Utilities.BytesToString(recordData, 0, 4);
if (magic != "FILE")
{
if (bitmap.IsPresent(index))
{
ReportError("Invalid MFT record magic at index {0} - was ({2},{3},{4},{5}) \"{1}\"", index, magic.Trim('\0'), (int)magic[0], (int)magic[1], (int)magic[2], (int)magic[3]);
}
}
else
{
if (!VerifyMftRecord(recordData, bitmap.IsPresent(index), bytesPerSector))
{
ReportError("Invalid MFT record at index {0}", index);
StringBuilder bldr = new StringBuilder();
for (int i = 0; i < recordData.Length; ++i)
{
bldr.Append(string.Format(CultureInfo.InvariantCulture, " {0:X2}", recordData[i]));
}
ReportInfo("MFT record binary data for index {0}:{1}", index, bldr.ToString());
}
}
index++;
}
}
}
private void VerifyMft()
{
// Cluster allocation check - check for double allocations
Dictionary<long, string> clusterMap = new Dictionary<long, string>();
foreach (FileRecord fr in _context.Mft.Records)
{
if ((fr.Flags & FileRecordFlags.InUse) != 0)
{
File f = new File(_context, fr);
foreach (NtfsAttribute attr in f.AllAttributes)
{
string attrKey = fr.MasterFileTableIndex + ":" + attr.Id;
foreach (var range in attr.GetClusters())
{
if (!VerifyClusterRange(range))
{
ReportError("Attribute {0} contains bad cluster range {1}", attrKey, range);
}
for (long cluster = range.Offset; cluster < range.Offset + range.Count; ++cluster)
{
string existingKey;
if (clusterMap.TryGetValue(cluster, out existingKey))
{
ReportError("Two attributes referencing cluster {0} (0x{0:X16}) - {1} and {2} (as MftIndex:AttrId)", cluster, existingKey, attrKey);
}
}
}
}
}
}
}
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;
}
private bool VerifyClusterRange(Range<long, long> range)
{
bool ok = true;
if (range.Offset < 0)
{
ReportError("Invalid cluster range {0} - negative start", range);
ok = false;
}
if (range.Count <= 0)
{
ReportError("Invalid cluster range {0} - negative/zero count", range);
ok = false;
}
if ((range.Offset + range.Count) * _context.BiosParameterBlock.BytesPerCluster > _context.RawStream.Length)
{
ReportError("Invalid cluster range {0} - beyond end of disk", range);
ok = false;
}
return ok;
}
private void ReportDump(IDiagnosticTraceable toDump)
{
_levelsDetected |= ReportLevels.Information;
if ((_reportLevels & ReportLevels.Information) != 0)
{
toDump.Dump(_report, "INFO: ");
}
}
private void ReportInfo(string str, params object[] args)
{
_levelsDetected |= ReportLevels.Information;
if ((_reportLevels & ReportLevels.Information) != 0)
{
_report.WriteLine("INFO: " + str, args);
}
}
private void ReportError(string str, params object[] args)
{
_levelsDetected |= ReportLevels.Errors;
if ((_reportLevels & ReportLevels.Errors) != 0)
{
_report.WriteLine("ERROR: " + str, args);
}
}
[Serializable]
private sealed class AbortException : InvalidFileSystemException
{
public AbortException()
: base()
{
}
private AbortException(SerializationInfo info, StreamingContext ctxt)
: base(info, ctxt)
{
}
}
}
}
+46
View File
@@ -0,0 +1,46 @@
//
// 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.Security.Principal;
/// <summary>
/// Class representing NTFS formatting options.
/// </summary>
public sealed class NtfsFormatOptions
{
/// <summary>
/// Gets or sets the NTFS bootloader code to put in the formatted file system.
/// </summary>
public byte[] BootCode { get; set; }
/// <summary>
/// Gets or sets the SID of the computer account that notionally formatted the file system.
/// </summary>
/// <remarks>
/// Certain ACLs in the file system will refer to the 'local' administrator of the indicated
/// computer account.
/// </remarks>
public SecurityIdentifier ComputerAccount { get; set; }
}
}
+341
View File
@@ -0,0 +1,341 @@
//
// 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.IO;
using System.Security.AccessControl;
using System.Security.Principal;
internal class NtfsFormatter
{
private int _clusterSize;
private int _mftRecordSize;
private int _indexBufferSize;
private long _bitmapCluster;
private long _mftMirrorCluster;
private long _mftCluster;
private NtfsContext _context;
public string Label { get; set; }
public Geometry DiskGeometry { get; set; }
public long FirstSector { get; set; }
public long SectorCount { get; set; }
public byte[] BootCode { get; set; }
public SecurityIdentifier ComputerAccount { get; set; }
public NtfsFileSystem Format(Stream stream)
{
_context = new NtfsContext();
_context.Options = new NtfsOptions();
_context.RawStream = stream;
_context.AttributeDefinitions = new AttributeDefinitions();
string localAdminString = (ComputerAccount == null)
? "LA"
: new SecurityIdentifier(WellKnownSidType.AccountAdministratorSid, ComputerAccount).ToString();
using (new NtfsTransaction())
{
_clusterSize = 4096;
_mftRecordSize = 1024;
_indexBufferSize = 4096;
long totalClusters = ((SectorCount - 1) * Sizes.Sector) / _clusterSize;
// Allocate a minimum of 8KB for the boot loader, but allow for more
int numBootClusters = Utilities.Ceil(Math.Max((int)(8 * Sizes.OneKiB), BootCode == null ? 0 : BootCode.Length), _clusterSize);
// Place MFT mirror in the middle of the volume
_mftMirrorCluster = totalClusters / 2;
uint numMftMirrorClusters = 1;
// The bitmap is also near the middle
_bitmapCluster = _mftMirrorCluster + 13;
int numBitmapClusters = (int)Utilities.Ceil(totalClusters / 8, _clusterSize);
// The MFT bitmap goes 'near' the start - approx 10% in - but ensure we avoid the bootloader
long mftBitmapCluster = Math.Max(3 + (totalClusters / 10), numBootClusters);
int numMftBitmapClusters = 1;
// The MFT follows it's bitmap
_mftCluster = mftBitmapCluster + numMftBitmapClusters;
int numMftClusters = 8;
if (_mftCluster + numMftClusters > _mftMirrorCluster
|| _bitmapCluster + numBitmapClusters >= totalClusters)
{
throw new IOException("Unable to determine initial layout of NTFS metadata - disk may be too small");
}
CreateBiosParameterBlock(stream, numBootClusters * _clusterSize);
_context.Mft = new MasterFileTable(_context);
File mftFile = _context.Mft.InitializeNew(_context, mftBitmapCluster, (ulong)numMftBitmapClusters, (long)_mftCluster, (ulong)numMftClusters);
File bitmapFile = CreateFixedSystemFile(MasterFileTable.BitmapIndex, _bitmapCluster, (ulong)numBitmapClusters, true);
_context.ClusterBitmap = new ClusterBitmap(bitmapFile);
_context.ClusterBitmap.MarkAllocated(0, numBootClusters);
_context.ClusterBitmap.MarkAllocated(_bitmapCluster, numBitmapClusters);
_context.ClusterBitmap.MarkAllocated(mftBitmapCluster, numMftBitmapClusters);
_context.ClusterBitmap.MarkAllocated(_mftCluster, numMftClusters);
_context.ClusterBitmap.SetTotalClusters(totalClusters);
bitmapFile.UpdateRecordInMft();
File mftMirrorFile = CreateFixedSystemFile(MasterFileTable.MftMirrorIndex, _mftMirrorCluster, numMftMirrorClusters, true);
File logFile = CreateSystemFile(MasterFileTable.LogFileIndex);
using (Stream s = logFile.OpenStream(AttributeType.Data, null, FileAccess.ReadWrite))
{
s.SetLength(Math.Min(Math.Max(2 * Sizes.OneMiB, (totalClusters / 500) * (long)_clusterSize), 64 * Sizes.OneMiB));
byte[] buffer = new byte[1024 * 1024];
for (int i = 0; i < buffer.Length; ++i)
{
buffer[i] = 0xFF;
}
long totalWritten = 0;
while (totalWritten < s.Length)
{
int toWrite = (int)Math.Min(s.Length - totalWritten, buffer.Length);
s.Write(buffer, 0, toWrite);
totalWritten += toWrite;
}
}
File volumeFile = CreateSystemFile(MasterFileTable.VolumeIndex);
NtfsStream volNameStream = volumeFile.CreateStream(AttributeType.VolumeName, null);
volNameStream.SetContent(new VolumeName(Label ?? "New Volume"));
NtfsStream volInfoStream = volumeFile.CreateStream(AttributeType.VolumeInformation, null);
volInfoStream.SetContent(new VolumeInformation(3, 1, VolumeInformationFlags.None));
SetSecurityAttribute(volumeFile, "O:" + localAdminString + "G:BAD:(A;;0x12019f;;;SY)(A;;0x12019f;;;BA)");
volumeFile.UpdateRecordInMft();
_context.GetFileByIndex = delegate(long index) { return new File(_context, _context.Mft.GetRecord(index, false)); };
_context.AllocateFile = delegate(FileRecordFlags frf) { return new File(_context, _context.Mft.AllocateRecord(frf, false)); };
File attrDefFile = CreateSystemFile(MasterFileTable.AttrDefIndex);
_context.AttributeDefinitions.WriteTo(attrDefFile);
SetSecurityAttribute(attrDefFile, "O:" + localAdminString + "G:BAD:(A;;FR;;;SY)(A;;FR;;;BA)");
attrDefFile.UpdateRecordInMft();
File bootFile = CreateFixedSystemFile(MasterFileTable.BootIndex, 0, (uint)numBootClusters, false);
SetSecurityAttribute(bootFile, "O:" + localAdminString + "G:BAD:(A;;FR;;;SY)(A;;FR;;;BA)");
bootFile.UpdateRecordInMft();
File badClusFile = CreateSystemFile(MasterFileTable.BadClusIndex);
badClusFile.CreateStream(AttributeType.Data, "$Bad");
badClusFile.UpdateRecordInMft();
File secureFile = CreateSystemFile(MasterFileTable.SecureIndex, FileRecordFlags.HasViewIndex);
secureFile.RemoveStream(secureFile.GetStream(AttributeType.Data, null));
_context.SecurityDescriptors = SecurityDescriptors.Initialize(secureFile);
secureFile.UpdateRecordInMft();
File upcaseFile = CreateSystemFile(MasterFileTable.UpCaseIndex);
_context.UpperCase = UpperCase.Initialize(upcaseFile);
upcaseFile.UpdateRecordInMft();
File objIdFile = File.CreateNew(_context, FileRecordFlags.IsMetaFile | FileRecordFlags.HasViewIndex, FileAttributeFlags.None);
objIdFile.RemoveStream(objIdFile.GetStream(AttributeType.Data, null));
objIdFile.CreateIndex("$O", (AttributeType)0, AttributeCollationRule.MultipleUnsignedLongs);
objIdFile.UpdateRecordInMft();
File reparseFile = File.CreateNew(_context, FileRecordFlags.IsMetaFile | FileRecordFlags.HasViewIndex, FileAttributeFlags.None);
reparseFile.CreateIndex("$R", (AttributeType)0, AttributeCollationRule.MultipleUnsignedLongs);
reparseFile.UpdateRecordInMft();
File quotaFile = File.CreateNew(_context, FileRecordFlags.IsMetaFile | FileRecordFlags.HasViewIndex, FileAttributeFlags.None);
Quotas.Initialize(quotaFile);
Directory extendDir = CreateSystemDirectory(MasterFileTable.ExtendIndex);
extendDir.AddEntry(objIdFile, "$ObjId", FileNameNamespace.Win32AndDos);
extendDir.AddEntry(reparseFile, "$Reparse", FileNameNamespace.Win32AndDos);
extendDir.AddEntry(quotaFile, "$Quota", FileNameNamespace.Win32AndDos);
extendDir.UpdateRecordInMft();
Directory rootDir = CreateSystemDirectory(MasterFileTable.RootDirIndex);
rootDir.AddEntry(mftFile, "$MFT", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(mftMirrorFile, "$MFTMirr", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(logFile, "$LogFile", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(volumeFile, "$Volume", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(attrDefFile, "$AttrDef", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(rootDir, ".", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(bitmapFile, "$Bitmap", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(bootFile, "$Boot", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(badClusFile, "$BadClus", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(secureFile, "$Secure", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(upcaseFile, "$UpCase", FileNameNamespace.Win32AndDos);
rootDir.AddEntry(extendDir, "$Extend", FileNameNamespace.Win32AndDos);
SetSecurityAttribute(rootDir, "O:" + localAdminString + "G:BUD:(A;OICI;FA;;;BA)(A;OICI;FA;;;SY)(A;OICIIO;GA;;;CO)(A;OICI;0x1200a9;;;BU)(A;CI;LC;;;BU)(A;CIIO;DC;;;BU)(A;;0x1200a9;;;WD)");
rootDir.UpdateRecordInMft();
// A number of records are effectively 'reserved'
for (long i = MasterFileTable.ExtendIndex + 1; i <= 15; i++)
{
File f = CreateSystemFile(i);
SetSecurityAttribute(f, "O:S-1-5-21-1708537768-746137067-1060284298-1003G:BAD:(A;;0x12019f;;;SY)(A;;0x12019f;;;BA)");
f.UpdateRecordInMft();
}
}
// XP-style security permissions setup
NtfsFileSystem ntfs = new NtfsFileSystem(stream);
ntfs.SetSecurity(@"$MFT", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;FR;;;SY)(A;;FR;;;BA)"));
ntfs.SetSecurity(@"$MFTMirr", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;FR;;;SY)(A;;FR;;;BA)"));
ntfs.SetSecurity(@"$LogFile", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;FR;;;SY)(A;;FR;;;BA)"));
ntfs.SetSecurity(@"$Bitmap", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;FR;;;SY)(A;;FR;;;BA)"));
ntfs.SetSecurity(@"$BadClus", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;FR;;;SY)(A;;FR;;;BA)"));
ntfs.SetSecurity(@"$UpCase", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;FR;;;SY)(A;;FR;;;BA)"));
ntfs.SetSecurity(@"$Secure", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;0x12019f;;;SY)(A;;0x12019f;;;BA)"));
ntfs.SetSecurity(@"$Extend", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;0x12019f;;;SY)(A;;0x12019f;;;BA)"));
ntfs.SetSecurity(@"$Extend\$Quota", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;0x12019f;;;SY)(A;;0x12019f;;;BA)"));
ntfs.SetSecurity(@"$Extend\$ObjId", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;0x12019f;;;SY)(A;;0x12019f;;;BA)"));
ntfs.SetSecurity(@"$Extend\$Reparse", new RawSecurityDescriptor("O:" + localAdminString + "G:BAD:(A;;0x12019f;;;SY)(A;;0x12019f;;;BA)"));
ntfs.CreateDirectory("System Volume Information");
ntfs.SetAttributes("System Volume Information", FileAttributes.Hidden | FileAttributes.System | FileAttributes.Directory);
ntfs.SetSecurity("System Volume Information", new RawSecurityDescriptor("O:BAG:SYD:(A;OICI;FA;;;SY)"));
using (Stream s = ntfs.OpenFile(@"System Volume Information\MountPointManagerRemoteDatabase", FileMode.Create))
{
}
ntfs.SetAttributes(@"System Volume Information\MountPointManagerRemoteDatabase", FileAttributes.Hidden | FileAttributes.System | FileAttributes.Archive);
ntfs.SetSecurity(@"System Volume Information\MountPointManagerRemoteDatabase", new RawSecurityDescriptor("O:BAG:SYD:(A;;FA;;;SY)"));
return ntfs;
}
private static void SetSecurityAttribute(File file, string secDesc)
{
NtfsStream rootSecurityStream = file.CreateStream(AttributeType.SecurityDescriptor, null);
SecurityDescriptor sd = new SecurityDescriptor();
sd.Descriptor = new RawSecurityDescriptor(secDesc);
rootSecurityStream.SetContent(sd);
}
private File CreateFixedSystemFile(long mftIndex, long firstCluster, ulong numClusters, bool wipe)
{
BiosParameterBlock bpb = _context.BiosParameterBlock;
if (wipe)
{
byte[] wipeBuffer = new byte[bpb.BytesPerCluster];
_context.RawStream.Position = firstCluster * bpb.BytesPerCluster;
for (ulong i = 0; i < numClusters; ++i)
{
_context.RawStream.Write(wipeBuffer, 0, wipeBuffer.Length);
}
}
FileRecord fileRec = _context.Mft.AllocateRecord((uint)mftIndex, FileRecordFlags.None);
fileRec.Flags = FileRecordFlags.InUse;
fileRec.SequenceNumber = (ushort)mftIndex;
File file = new File(_context, fileRec);
StandardInformation.InitializeNewFile(file, FileAttributeFlags.Hidden | FileAttributeFlags.System);
file.CreateStream(AttributeType.Data, null, firstCluster, numClusters, (uint)bpb.BytesPerCluster);
file.UpdateRecordInMft();
if (_context.ClusterBitmap != null)
{
_context.ClusterBitmap.MarkAllocated(firstCluster, (long)numClusters);
}
return file;
}
private File CreateSystemFile(long mftIndex)
{
return CreateSystemFile(mftIndex, FileRecordFlags.None);
}
private File CreateSystemFile(long mftIndex, FileRecordFlags flags)
{
FileRecord fileRec = _context.Mft.AllocateRecord((uint)mftIndex, flags);
fileRec.SequenceNumber = (ushort)mftIndex;
File file = new File(_context, fileRec);
StandardInformation.InitializeNewFile(file, FileAttributeFlags.Hidden | FileAttributeFlags.System | FileRecord.ConvertFlags(flags));
file.CreateStream(AttributeType.Data, null);
file.UpdateRecordInMft();
return file;
}
private Directory CreateSystemDirectory(long mftIndex)
{
FileRecord fileRec = _context.Mft.AllocateRecord((uint)mftIndex, FileRecordFlags.None);
fileRec.Flags = FileRecordFlags.InUse | FileRecordFlags.IsDirectory;
fileRec.SequenceNumber = (ushort)mftIndex;
Directory dir = new Directory(_context, fileRec);
StandardInformation.InitializeNewFile(dir, FileAttributeFlags.Hidden | FileAttributeFlags.System);
dir.CreateIndex("$I30", AttributeType.FileName, AttributeCollationRule.Filename);
dir.UpdateRecordInMft();
return dir;
}
private void CreateBiosParameterBlock(Stream stream, int bootFileSize)
{
byte[] bootSectors = new byte[bootFileSize];
if (BootCode != null)
{
Array.Copy(BootCode, 0, bootSectors, 0, BootCode.Length);
}
BiosParameterBlock bpb = BiosParameterBlock.Initialized(DiskGeometry, _clusterSize, (uint)FirstSector, SectorCount, _mftRecordSize, _indexBufferSize);
bpb.MftCluster = _mftCluster;
bpb.MftMirrorCluster = _mftMirrorCluster;
bpb.ToBytes(bootSectors, 0);
// Primary goes at the start of the partition
stream.Position = 0;
stream.Write(bootSectors, 0, bootSectors.Length);
// Backup goes at the end of the data in the partition
stream.Position = (SectorCount - 1) * Sizes.Sector;
stream.Write(bootSectors, 0, Sizes.Sector);
_context.BiosParameterBlock = bpb;
}
}
}
+141
View File
@@ -0,0 +1,141 @@
//
// 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 DiscUtils.Compression;
/// <summary>
/// Class whose instances hold options controlling how <see cref="NtfsFileSystem"/> works.
/// </summary>
public sealed class NtfsOptions : DiscFileSystemOptions
{
private bool _hideMetaFiles;
private bool _hideHiddenFiles;
private bool _hideSystemFiles;
private bool _hideDosFileNames;
private ShortFileNameOption _shortNameCreation;
private BlockCompressor _compressor;
private bool _readCache;
private bool _fileLengthFromDirectoryEntries;
internal NtfsOptions()
{
_hideMetaFiles = true;
_hideHiddenFiles = true;
_hideSystemFiles = true;
_hideDosFileNames = true;
_compressor = new LZNT1();
_readCache = true;
_fileLengthFromDirectoryEntries = true;
}
/// <summary>
/// Gets or sets a value indicating whether to include file system meta-files when enumerating directories.
/// </summary>
/// <remarks>Meta-files are those with an MFT (Master File Table) index less than 24.</remarks>
public bool HideMetafiles
{
get { return _hideMetaFiles; }
set { _hideMetaFiles = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to include hidden files when enumerating directories.
/// </summary>
public bool HideHiddenFiles
{
get { return _hideHiddenFiles; }
set { _hideHiddenFiles = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to include system files when enumerating directories.
/// </summary>
public bool HideSystemFiles
{
get { return _hideSystemFiles; }
set { _hideSystemFiles = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to hide DOS (8.3-style) file names when enumerating directories.
/// </summary>
public bool HideDosFileNames
{
get { return _hideDosFileNames; }
set { _hideDosFileNames = value; }
}
/// <summary>
/// Gets or sets a value indicating whether short (8.3) file names are created automatically.
/// </summary>
public ShortFileNameOption ShortNameCreation
{
get { return _shortNameCreation; }
set { _shortNameCreation = value; }
}
/// <summary>
/// Gets or sets the compression algorithm used for compressing files.
/// </summary>
public BlockCompressor Compressor
{
get { return _compressor; }
set { _compressor = value; }
}
/// <summary>
/// Gets or sets a value indicating whether NTFS-level read caching is used.
/// </summary>
public bool ReadCacheEnabled
{
get { return _readCache; }
set { _readCache = value; }
}
/// <summary>
/// Gets or sets a value indicating whether file length information comes from directory entries or file data.
/// </summary>
/// <remarks>
/// <para>The default (<c>true</c>) is that file length information is supplied by the directory entry
/// for a file. In some circumstances that information may be inaccurate - specifically for files with multiple
/// hard links, the directory entries are only updated for the hard link used to open the file.</para>
/// <para>Setting this value to <c>false</c>, will always retrieve the latest information from the underlying
/// NTFS attribute information, which reflects the true size of the file.</para>
/// </remarks>
public bool FileLengthFromDirectoryEntries
{
get { return _fileLengthFromDirectoryEntries; }
set { _fileLengthFromDirectoryEntries = value; }
}
/// <summary>
/// Returns a string representation of the file system options.
/// </summary>
/// <returns>A string of the form Show: XX XX XX.</returns>
public override string ToString()
{
return "Show: Normal " + (HideMetafiles ? string.Empty : "Meta ") + (HideHiddenFiles ? string.Empty : "Hidden ") + (HideSystemFiles ? string.Empty : "System ") + (HideDosFileNames ? string.Empty : "ShortNames ");
}
}
}
+122
View File
@@ -0,0 +1,122 @@
//
// 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.IO;
internal class NtfsStream
{
private File _file;
private NtfsAttribute _attr;
public NtfsStream(File file, NtfsAttribute attr)
{
_file = file;
_attr = attr;
}
public NtfsAttribute Attribute
{
get { return _attr; }
}
public AttributeType AttributeType
{
get { return _attr.Type; }
}
public string Name
{
get { return _attr.Name; }
}
/// <summary>
/// Gets the content of a stream.
/// </summary>
/// <typeparam name="T">The stream's content structure.</typeparam>
/// <returns>The content.</returns>
public T GetContent<T>()
where T : IByteArraySerializable, IDiagnosticTraceable, new()
{
byte[] buffer;
using (Stream s = Open(FileAccess.Read))
{
buffer = Utilities.ReadFully(s, (int)s.Length);
}
T value = new T();
value.ReadFrom(buffer, 0);
return value;
}
/// <summary>
/// Sets the content of a stream.
/// </summary>
/// <typeparam name="T">The stream's content structure.</typeparam>
/// <param name="value">The new value for the stream.</param>
public void SetContent<T>(T value)
where T : IByteArraySerializable, IDiagnosticTraceable, new()
{
byte[] buffer = new byte[value.Size];
value.WriteTo(buffer, 0);
using (Stream s = Open(FileAccess.Write))
{
s.Write(buffer, 0, buffer.Length);
s.SetLength(buffer.Length);
}
}
public SparseStream Open(FileAccess access)
{
return _attr.Open(access);
}
internal Range<long, long>[] GetClusters()
{
return _attr.GetClusters();
}
internal StreamExtent[] GetAbsoluteExtents()
{
List<StreamExtent> result = new List<StreamExtent>();
long clusterSize = _file.Context.BiosParameterBlock.BytesPerCluster;
if (_attr.IsNonResident)
{
Range<long, long>[] clusters = _attr.GetClusters();
foreach (var clusterRange in clusters)
{
result.Add(new StreamExtent(clusterRange.Offset * clusterSize, clusterRange.Count * clusterSize));
}
}
else
{
result.Add(new StreamExtent(_attr.OffsetToAbsolutePos(0), _attr.Length));
}
return result.ToArray();
}
}
}
+66
View File
@@ -0,0 +1,66 @@
//
// 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.Text;
internal sealed class NtfsTransaction : IDisposable
{
[ThreadStatic]
private static NtfsTransaction s_instance;
private bool _ownRecord;
private DateTime _timestamp;
public NtfsTransaction()
{
if (s_instance == null)
{
s_instance = this;
_timestamp = DateTime.UtcNow;
_ownRecord = true;
}
}
public static NtfsTransaction Current
{
get { return s_instance; }
}
public DateTime Timestamp
{
get { return _timestamp; }
}
public void Dispose()
{
if (_ownRecord)
{
s_instance = null;
}
}
}
}
+53
View File
@@ -0,0 +1,53 @@
//
// 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.IO;
internal sealed class ObjectId : IByteArraySerializable, IDiagnosticTraceable
{
public Guid Id;
public int Size
{
get { return 16; }
}
public int ReadFrom(byte[] buffer, int offset)
{
Id = Utilities.ToGuidLittleEndian(buffer, offset);
return 16;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(Id, buffer, offset);
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + " Object ID: " + Id);
}
}
}
+64
View File
@@ -0,0 +1,64 @@
//
// 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.Globalization;
internal sealed class ObjectIdRecord : IByteArraySerializable
{
public FileRecordReference MftReference;
public Guid BirthVolumeId;
public Guid BirthObjectId;
public Guid BirthDomainId;
public int Size
{
get { return 0x38; }
}
public int ReadFrom(byte[] buffer, int offset)
{
MftReference = new FileRecordReference();
MftReference.ReadFrom(buffer, offset);
BirthVolumeId = Utilities.ToGuidLittleEndian(buffer, offset + 0x08);
BirthObjectId = Utilities.ToGuidLittleEndian(buffer, offset + 0x18);
BirthDomainId = Utilities.ToGuidLittleEndian(buffer, offset + 0x28);
return 0x38;
}
public void WriteTo(byte[] buffer, int offset)
{
MftReference.WriteTo(buffer, offset);
Utilities.WriteBytesLittleEndian(BirthVolumeId, buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian(BirthObjectId, buffer, offset + 0x18);
Utilities.WriteBytesLittleEndian(BirthDomainId, buffer, offset + 0x28);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[Data-MftRef:{0},BirthVolId:{1},BirthObjId:{2},BirthDomId:{3}]", MftReference, BirthVolumeId, BirthObjectId, BirthDomainId);
}
}
}
+125
View File
@@ -0,0 +1,125 @@
//
// 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;
internal sealed class ObjectIds
{
private IndexView<IndexKey, ObjectIdRecord> _index;
private File _file;
public ObjectIds(File file)
{
_file = file;
_index = new IndexView<IndexKey, ObjectIdRecord>(file.GetIndex("$O"));
}
internal IEnumerable<KeyValuePair<Guid, ObjectIdRecord>> All
{
get
{
foreach (var record in _index.Entries)
{
yield return new KeyValuePair<Guid, ObjectIdRecord>(record.Key.Id, record.Value);
}
}
}
internal void Add(Guid objId, FileRecordReference mftRef, Guid birthId, Guid birthVolumeId, Guid birthDomainId)
{
IndexKey newKey = new IndexKey();
newKey.Id = objId;
ObjectIdRecord newData = new ObjectIdRecord();
newData.MftReference = mftRef;
newData.BirthObjectId = birthId;
newData.BirthVolumeId = birthVolumeId;
newData.BirthDomainId = birthDomainId;
_index[newKey] = newData;
_file.UpdateRecordInMft();
}
internal void Remove(Guid objId)
{
IndexKey key = new IndexKey();
key.Id = objId;
_index.Remove(key);
_file.UpdateRecordInMft();
}
internal bool TryGetValue(Guid objId, out ObjectIdRecord value)
{
IndexKey key = new IndexKey();
key.Id = objId;
return _index.TryGetValue(key, out value);
}
internal void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "OBJECT ID INDEX");
foreach (var entry in _index.Entries)
{
writer.WriteLine(indent + " OBJECT ID INDEX ENTRY");
writer.WriteLine(indent + " Id: " + entry.Key.Id);
writer.WriteLine(indent + " MFT Reference: " + entry.Value.MftReference);
writer.WriteLine(indent + " Birth Volume: " + entry.Value.BirthVolumeId);
writer.WriteLine(indent + " Birth Id: " + entry.Value.BirthObjectId);
writer.WriteLine(indent + " Birth Domain: " + entry.Value.BirthDomainId);
}
}
internal sealed class IndexKey : IByteArraySerializable
{
public Guid Id;
public int Size
{
get { return 16; }
}
public int ReadFrom(byte[] buffer, int offset)
{
Id = Utilities.ToGuidLittleEndian(buffer, offset + 0);
return 16;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(Id, buffer, offset + 0);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[Key-Id:{0}]", Id);
}
}
}
}
+227
View File
@@ -0,0 +1,227 @@
//
// 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.Globalization;
using System.IO;
using System.Security.Principal;
internal sealed class Quotas
{
private IndexView<OwnerKey, OwnerRecord> _ownerIndex;
private IndexView<OwnerRecord, QuotaRecord> _quotaIndex;
public Quotas(File file)
{
_ownerIndex = new IndexView<OwnerKey, OwnerRecord>(file.GetIndex("$O"));
_quotaIndex = new IndexView<OwnerRecord, QuotaRecord>(file.GetIndex("$Q"));
}
public static Quotas Initialize(File file)
{
Index ownerIndex = file.CreateIndex("$O", (AttributeType)0, AttributeCollationRule.Sid);
Index quotaIndox = file.CreateIndex("$Q", (AttributeType)0, AttributeCollationRule.UnsignedLong);
IndexView<OwnerKey, OwnerRecord> ownerIndexView = new IndexView<OwnerKey, OwnerRecord>(ownerIndex);
IndexView<OwnerRecord, QuotaRecord> quotaIndexView = new IndexView<OwnerRecord, QuotaRecord>(quotaIndox);
OwnerKey adminSid = new OwnerKey(new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null));
OwnerRecord adminOwnerId = new OwnerRecord(256);
ownerIndexView[adminSid] = adminOwnerId;
quotaIndexView[new OwnerRecord(1)] = new QuotaRecord(null);
quotaIndexView[adminOwnerId] = new QuotaRecord(adminSid.Sid);
return new Quotas(file);
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "QUOTAS");
writer.WriteLine(indent + " OWNER INDEX");
foreach (var entry in _ownerIndex.Entries)
{
writer.WriteLine(indent + " OWNER INDEX ENTRY");
writer.WriteLine(indent + " SID: " + entry.Key.Sid);
writer.WriteLine(indent + " Owner Id: " + entry.Value.OwnerId);
}
writer.WriteLine(indent + " QUOTA INDEX");
foreach (var entry in _quotaIndex.Entries)
{
writer.WriteLine(indent + " QUOTA INDEX ENTRY");
writer.WriteLine(indent + " Owner Id: " + entry.Key.OwnerId);
writer.WriteLine(indent + " User SID: " + entry.Value.Sid);
writer.WriteLine(indent + " Changed: " + entry.Value.ChangeTime);
writer.WriteLine(indent + " Exceeded: " + entry.Value.ExceededTime);
writer.WriteLine(indent + " Bytes Used: " + entry.Value.BytesUsed);
writer.WriteLine(indent + " Flags: " + entry.Value.Flags);
writer.WriteLine(indent + " Hard Limit: " + entry.Value.HardLimit);
writer.WriteLine(indent + " Warning Limit: " + entry.Value.WarningLimit);
writer.WriteLine(indent + " Version: " + entry.Value.Version);
}
}
internal sealed class OwnerKey : IByteArraySerializable
{
public SecurityIdentifier Sid;
public OwnerKey()
{
}
public OwnerKey(SecurityIdentifier sid)
{
Sid = sid;
}
public int Size
{
get { return Sid.BinaryLength; }
}
public int ReadFrom(byte[] buffer, int offset)
{
Sid = new SecurityIdentifier(buffer, offset);
return Sid.BinaryLength;
}
public void WriteTo(byte[] buffer, int offset)
{
Sid.GetBinaryForm(buffer, offset);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[Sid:{0}]", Sid);
}
}
internal sealed class OwnerRecord : IByteArraySerializable
{
public int OwnerId;
public OwnerRecord()
{
}
public OwnerRecord(int ownerId)
{
OwnerId = ownerId;
}
public int Size
{
get { return 4; }
}
public int ReadFrom(byte[] buffer, int offset)
{
OwnerId = Utilities.ToInt32LittleEndian(buffer, offset);
return 4;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(OwnerId, buffer, offset);
}
public override string ToString()
{
return "[OwnerId:" + OwnerId + "]";
}
}
internal sealed class QuotaRecord : IByteArraySerializable
{
public int Version;
public int Flags;
public long BytesUsed;
public DateTime ChangeTime;
public long WarningLimit;
public long HardLimit;
public long ExceededTime;
public SecurityIdentifier Sid;
public QuotaRecord()
{
}
public QuotaRecord(SecurityIdentifier sid)
{
Version = 2;
Flags = 1;
ChangeTime = DateTime.UtcNow;
WarningLimit = -1;
HardLimit = -1;
Sid = sid;
}
public int Size
{
get { return 0x30 + (Sid == null ? 0 : Sid.BinaryLength); }
}
public int ReadFrom(byte[] buffer, int offset)
{
Version = Utilities.ToInt32LittleEndian(buffer, offset);
Flags = Utilities.ToInt32LittleEndian(buffer, offset + 0x04);
BytesUsed = Utilities.ToInt64LittleEndian(buffer, offset + 0x08);
ChangeTime = DateTime.FromFileTimeUtc(Utilities.ToInt64LittleEndian(buffer, offset + 0x10));
WarningLimit = Utilities.ToInt64LittleEndian(buffer, offset + 0x18);
HardLimit = Utilities.ToInt64LittleEndian(buffer, offset + 0x20);
ExceededTime = Utilities.ToInt64LittleEndian(buffer, offset + 0x28);
if (buffer.Length - offset > 0x30)
{
Sid = new SecurityIdentifier(buffer, offset + 0x30);
return 0x30 + Sid.BinaryLength;
}
return 0x30;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(Version, buffer, offset);
Utilities.WriteBytesLittleEndian(Flags, buffer, offset + 0x04);
Utilities.WriteBytesLittleEndian(BytesUsed, buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian(ChangeTime.ToFileTimeUtc(), buffer, offset + 0x10);
Utilities.WriteBytesLittleEndian(WarningLimit, buffer, offset + 0x18);
Utilities.WriteBytesLittleEndian(HardLimit, buffer, offset + 0x20);
Utilities.WriteBytesLittleEndian(ExceededTime, buffer, offset + 0x28);
if (Sid != null)
{
Sid.GetBinaryForm(buffer, offset + 0x30);
}
}
public override string ToString()
{
return "[V:" + Version + ",F:" + Flags + ",BU:" + BytesUsed + ",CT:" + ChangeTime + ",WL:" + WarningLimit + ",HL:" + HardLimit + ",ET:" + ExceededTime + ",SID:" + Sid + "]";
}
}
}
}
+368
View File
@@ -0,0 +1,368 @@
//
// 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;
using DiscUtils;
/// <summary>
/// Low-level non-resident attribute operations.
/// </summary>
/// <remarks>
/// Responsible for:
/// * Cluster Allocation / Release
/// * Reading clusters from disk
/// * Writing clusters to disk
/// * Substituting zeros for 'sparse'/'unallocated' clusters
/// Not responsible for:
/// * Compression / Decompression
/// * Extending attributes.
/// </remarks>
internal sealed class RawClusterStream : ClusterStream
{
private INtfsContext _context;
private Stream _fsStream;
private int _bytesPerCluster;
private CookedDataRuns _cookedRuns;
private bool _isMft;
public RawClusterStream(INtfsContext context, CookedDataRuns cookedRuns, bool isMft)
{
_context = context;
_cookedRuns = cookedRuns;
_isMft = isMft;
_fsStream = _context.RawStream;
_bytesPerCluster = context.BiosParameterBlock.BytesPerCluster;
}
public override long AllocatedClusterCount
{
get
{
long total = 0;
for (int i = 0; i < _cookedRuns.Count; ++i)
{
CookedDataRun run = _cookedRuns[i];
total += run.IsSparse ? 0 : run.Length;
}
return total;
}
}
public override IEnumerable<Range<long, long>> StoredClusters
{
get
{
Range<long, long> lastVcnRange = null;
List<Range<long, long>> ranges = new List<Range<long, long>>();
int runCount = _cookedRuns.Count;
for (int i = 0; i < runCount; i++)
{
CookedDataRun cookedRun = _cookedRuns[i];
if (!cookedRun.IsSparse)
{
long startPos = cookedRun.StartVcn;
if (lastVcnRange != null && lastVcnRange.Offset + lastVcnRange.Count == startPos)
{
lastVcnRange = new Range<long, long>(lastVcnRange.Offset, lastVcnRange.Count + cookedRun.Length);
ranges[ranges.Count - 1] = lastVcnRange;
}
else
{
lastVcnRange = new Range<long, long>(cookedRun.StartVcn, cookedRun.Length);
ranges.Add(lastVcnRange);
}
}
}
return ranges;
}
}
public override bool IsClusterStored(long vcn)
{
int runIdx = _cookedRuns.FindDataRun(vcn, 0);
return !_cookedRuns[runIdx].IsSparse;
}
public bool AreAllClustersStored(long vcn, int count)
{
int runIdx = 0;
long focusVcn = vcn;
while (focusVcn < vcn + count)
{
runIdx = _cookedRuns.FindDataRun(focusVcn, runIdx);
CookedDataRun run = _cookedRuns[runIdx];
if (run.IsSparse)
{
return false;
}
focusVcn = run.StartVcn + run.Length;
}
return true;
}
public override void ExpandToClusters(long numVirtualClusters, NonResidentAttributeRecord extent, bool allocate)
{
long totalVirtualClusters = _cookedRuns.NextVirtualCluster;
if (totalVirtualClusters < numVirtualClusters)
{
NonResidentAttributeRecord realExtent = extent;
if (realExtent == null)
{
realExtent = _cookedRuns.Last.AttributeExtent;
}
DataRun newRun = new DataRun(0, numVirtualClusters - totalVirtualClusters, true);
realExtent.DataRuns.Add(newRun);
_cookedRuns.Append(newRun, extent);
realExtent.LastVcn = numVirtualClusters - 1;
}
if (allocate)
{
AllocateClusters(totalVirtualClusters, (int)(numVirtualClusters - totalVirtualClusters));
}
}
public override void TruncateToClusters(long numVirtualClusters)
{
if (numVirtualClusters < _cookedRuns.NextVirtualCluster)
{
ReleaseClusters(numVirtualClusters, (int)(_cookedRuns.NextVirtualCluster - numVirtualClusters));
int runIdx = _cookedRuns.FindDataRun(numVirtualClusters, 0);
if (numVirtualClusters != _cookedRuns[runIdx].StartVcn)
{
_cookedRuns.SplitRun(runIdx, numVirtualClusters);
runIdx++;
}
_cookedRuns.TruncateAt(runIdx);
}
}
public int AllocateClusters(long startVcn, int count)
{
if (startVcn + count > _cookedRuns.NextVirtualCluster)
{
throw new IOException("Attempt to allocate unknown clusters");
}
int totalAllocated = 0;
int runIdx = 0;
long focus = startVcn;
while (focus < startVcn + count)
{
runIdx = _cookedRuns.FindDataRun(focus, runIdx);
CookedDataRun run = _cookedRuns[runIdx];
if (run.IsSparse)
{
if (focus != run.StartVcn)
{
_cookedRuns.SplitRun(runIdx, focus);
runIdx++;
run = _cookedRuns[runIdx];
}
long numClusters = Math.Min((startVcn + count) - focus, run.Length);
if (numClusters != run.Length)
{
_cookedRuns.SplitRun(runIdx, focus + numClusters);
run = _cookedRuns[runIdx];
}
long nextCluster = -1;
for (int i = runIdx - 1; i >= 0; --i)
{
if (!_cookedRuns[i].IsSparse)
{
nextCluster = _cookedRuns[i].StartLcn + _cookedRuns[i].Length;
break;
}
}
var alloced = _context.ClusterBitmap.AllocateClusters(numClusters, nextCluster, _isMft, AllocatedClusterCount);
List<DataRun> runs = new List<DataRun>();
long lcn = runIdx == 0 ? 0 : _cookedRuns[runIdx - 1].StartLcn;
foreach (var allocation in alloced)
{
runs.Add(new DataRun(allocation.First - lcn, allocation.Second, false));
lcn = allocation.First;
}
_cookedRuns.MakeNonSparse(runIdx, runs);
totalAllocated += (int)numClusters;
focus += numClusters;
}
else
{
focus = run.StartVcn + run.Length;
}
}
return totalAllocated;
}
public int ReleaseClusters(long startVcn, int count)
{
int runIdx = 0;
int totalReleased = 0;
long focus = startVcn;
while (focus < startVcn + count)
{
runIdx = _cookedRuns.FindDataRun(focus, runIdx);
CookedDataRun run = _cookedRuns[runIdx];
if (run.IsSparse)
{
focus += run.Length;
}
else
{
if (focus != run.StartVcn)
{
_cookedRuns.SplitRun(runIdx, focus);
runIdx++;
run = _cookedRuns[runIdx];
}
long numClusters = Math.Min((startVcn + count) - focus, run.Length);
if (numClusters != run.Length)
{
_cookedRuns.SplitRun(runIdx, focus + numClusters);
run = _cookedRuns[runIdx];
}
_context.ClusterBitmap.FreeClusters(new Range<long, long>(run.StartLcn, run.Length));
_cookedRuns.MakeSparse(runIdx);
totalReleased += (int)run.Length;
focus += numClusters;
}
}
return totalReleased;
}
public override void ReadClusters(long startVcn, int count, byte[] buffer, int offset)
{
Utilities.AssertBufferParameters(buffer, offset, count * _bytesPerCluster);
int runIdx = 0;
int totalRead = 0;
while (totalRead < count)
{
long focusVcn = startVcn + totalRead;
runIdx = _cookedRuns.FindDataRun(focusVcn, runIdx);
CookedDataRun run = _cookedRuns[runIdx];
int toRead = (int)Math.Min(count - totalRead, run.Length - (focusVcn - run.StartVcn));
if (run.IsSparse)
{
Array.Clear(buffer, offset + (totalRead * _bytesPerCluster), toRead * _bytesPerCluster);
}
else
{
long lcn = _cookedRuns[runIdx].StartLcn + (focusVcn - run.StartVcn);
_fsStream.Position = lcn * _bytesPerCluster;
int numRead = Utilities.ReadFully(_fsStream, buffer, offset + (totalRead * _bytesPerCluster), toRead * _bytesPerCluster);
if (numRead != toRead * _bytesPerCluster)
{
throw new IOException(string.Format(CultureInfo.InvariantCulture, "Short read, reading {0} clusters starting at LCN {1}", toRead, lcn));
}
}
totalRead += toRead;
}
}
public override int WriteClusters(long startVcn, int count, byte[] buffer, int offset)
{
Utilities.AssertBufferParameters(buffer, offset, count * _bytesPerCluster);
int runIdx = 0;
int totalWritten = 0;
while (totalWritten < count)
{
long focusVcn = startVcn + totalWritten;
runIdx = _cookedRuns.FindDataRun(focusVcn, runIdx);
CookedDataRun run = _cookedRuns[runIdx];
if (run.IsSparse)
{
throw new NotImplementedException("Writing to sparse datarun");
}
int toWrite = (int)Math.Min(count - totalWritten, run.Length - (focusVcn - run.StartVcn));
long lcn = _cookedRuns[runIdx].StartLcn + (focusVcn - run.StartVcn);
_fsStream.Position = lcn * _bytesPerCluster;
_fsStream.Write(buffer, offset + (totalWritten * _bytesPerCluster), toWrite * _bytesPerCluster);
totalWritten += toWrite;
}
return 0;
}
public override int ClearClusters(long startVcn, int count)
{
byte[] zeroBuffer = new byte[16 * _bytesPerCluster];
int clustersAllocated = 0;
int numWritten = 0;
while (numWritten < count)
{
int toWrite = Math.Min(count - numWritten, 16);
clustersAllocated += WriteClusters(startVcn + numWritten, toWrite, zeroBuffer, 0);
numWritten += toWrite;
}
return -clustersAllocated;
}
}
}
+69
View File
@@ -0,0 +1,69 @@
//
// 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.Globalization;
using System.IO;
internal sealed class ReparsePointRecord : IByteArraySerializable, IDiagnosticTraceable
{
public uint Tag;
public byte[] Content;
public int Size
{
get { return 8 + Content.Length; }
}
public int ReadFrom(byte[] buffer, int offset)
{
Tag = Utilities.ToUInt32LittleEndian(buffer, offset);
ushort length = Utilities.ToUInt16LittleEndian(buffer, offset + 4);
Content = new byte[length];
Array.Copy(buffer, offset + 8, Content, 0, length);
return 8 + length;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(Tag, buffer, offset);
Utilities.WriteBytesLittleEndian((ushort)Content.Length, buffer, offset + 4);
Utilities.WriteBytesLittleEndian((ushort)0, buffer, offset + 6);
Array.Copy(Content, 0, buffer, offset + 8, Content.Length);
}
public void Dump(TextWriter writer, string linePrefix)
{
writer.WriteLine(linePrefix + " Tag: " + Tag.ToString("x", CultureInfo.InvariantCulture));
string hex = string.Empty;
for (int i = 0; i < Math.Min(Content.Length, 32); ++i)
{
hex = hex + string.Format(CultureInfo.InvariantCulture, " {0:X2}", Content[i]);
}
writer.WriteLine(linePrefix + " Data:" + hex + (Content.Length > 32 ? "..." : string.Empty));
}
}
}
+125
View File
@@ -0,0 +1,125 @@
//
// 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.Globalization;
using System.IO;
internal class ReparsePoints
{
private IndexView<Key, Data> _index;
private File _file;
public ReparsePoints(File file)
{
_file = file;
_index = new IndexView<Key, Data>(file.GetIndex("$R"));
}
internal void Add(uint tag, FileRecordReference file)
{
Key newKey = new Key();
newKey.Tag = tag;
newKey.File = file;
Data data = new Data();
_index[newKey] = data;
_file.UpdateRecordInMft();
}
internal void Remove(uint tag, FileRecordReference file)
{
Key key = new Key();
key.Tag = tag;
key.File = file;
_index.Remove(key);
_file.UpdateRecordInMft();
}
internal void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "REPARSE POINT INDEX");
foreach (var entry in _index.Entries)
{
writer.WriteLine(indent + " REPARSE POINT INDEX ENTRY");
writer.WriteLine(indent + " Tag: " + entry.Key.Tag.ToString("x", CultureInfo.InvariantCulture));
writer.WriteLine(indent + " MFT Reference: " + entry.Key.File);
}
}
internal sealed class Key : IByteArraySerializable
{
public uint Tag;
public FileRecordReference File;
public int Size
{
get { return 12; }
}
public int ReadFrom(byte[] buffer, int offset)
{
Tag = Utilities.ToUInt32LittleEndian(buffer, offset);
File = new FileRecordReference(Utilities.ToUInt64LittleEndian(buffer, offset + 4));
return 12;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(Tag, buffer, offset);
Utilities.WriteBytesLittleEndian(File.Value, buffer, offset + 4);
////Utilities.WriteBytesLittleEndian((uint)0, buffer, offset + 12);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0:x}:", Tag) + File;
}
}
internal sealed class Data : IByteArraySerializable
{
public int Size
{
get { return 0; }
}
public int ReadFrom(byte[] buffer, int offset)
{
return 0;
}
public void WriteTo(byte[] buffer, int offset)
{
}
public override string ToString()
{
return "<no data>";
}
}
}
}
+177
View File
@@ -0,0 +1,177 @@
//
// 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.IO;
using System.Text;
internal sealed class ResidentAttributeRecord : AttributeRecord
{
private byte _indexedFlag;
private SparseMemoryBuffer _memoryBuffer;
public ResidentAttributeRecord(byte[] buffer, int offset, out int length)
{
Read(buffer, offset, out length);
}
public ResidentAttributeRecord(AttributeType type, string name, ushort id, bool indexed, AttributeFlags flags)
: base(type, name, id, flags)
{
_nonResidentFlag = 0;
_indexedFlag = (byte)(indexed ? 1 : 0);
_memoryBuffer = new SparseMemoryBuffer(1024);
}
public override long AllocatedLength
{
get { return Utilities.RoundUp(DataLength, 8); }
set { throw new NotSupportedException(); }
}
public override long StartVcn
{
get { return 0; }
}
public override long DataLength
{
get { return _memoryBuffer.Capacity; }
set { throw new NotSupportedException(); }
}
/// <summary>
/// The amount of initialized data in the attribute (in bytes).
/// </summary>
public override long InitializedDataLength
{
get { return (long)DataLength; }
set { throw new NotSupportedException(); }
}
public override int Size
{
get
{
byte nameLength = 0;
ushort nameOffset = 0x18;
if (Name != null)
{
nameLength = (byte)Name.Length;
}
ushort dataOffset = (ushort)Utilities.RoundUp(nameOffset + (nameLength * 2), 8);
return (int)Utilities.RoundUp(dataOffset + _memoryBuffer.Capacity, 8);
}
}
public int DataOffset
{
get
{
byte nameLength = 0;
if (Name != null)
{
nameLength = (byte)Name.Length;
}
return Utilities.RoundUp(0x18 + (nameLength * 2), 8);
}
}
public IBuffer DataBuffer
{
get { return _memoryBuffer; }
}
public override IBuffer GetReadOnlyDataBuffer(INtfsContext context)
{
return _memoryBuffer;
}
public override Range<long, long>[] GetClusters()
{
return new Range<long, long>[0];
}
public override int Write(byte[] buffer, int offset)
{
byte nameLength = 0;
ushort nameOffset = 0;
if (Name != null)
{
nameOffset = 0x18;
nameLength = (byte)Name.Length;
}
ushort dataOffset = (ushort)Utilities.RoundUp(0x18 + (nameLength * 2), 8);
int length = (int)Utilities.RoundUp(dataOffset + _memoryBuffer.Capacity, 8);
Utilities.WriteBytesLittleEndian((uint)_type, buffer, offset + 0x00);
Utilities.WriteBytesLittleEndian(length, buffer, offset + 0x04);
buffer[offset + 0x08] = _nonResidentFlag;
buffer[offset + 0x09] = nameLength;
Utilities.WriteBytesLittleEndian(nameOffset, buffer, offset + 0x0A);
Utilities.WriteBytesLittleEndian((ushort)_flags, buffer, offset + 0x0C);
Utilities.WriteBytesLittleEndian(_attributeId, buffer, offset + 0x0E);
Utilities.WriteBytesLittleEndian((int)_memoryBuffer.Capacity, buffer, offset + 0x10);
Utilities.WriteBytesLittleEndian(dataOffset, buffer, offset + 0x14);
buffer[offset + 0x16] = _indexedFlag;
buffer[offset + 0x17] = 0; // Padding
if (Name != null)
{
Array.Copy(Encoding.Unicode.GetBytes(Name), 0, buffer, offset + nameOffset, nameLength * 2);
}
_memoryBuffer.Read(0, buffer, offset + dataOffset, (int)_memoryBuffer.Capacity);
return (int)length;
}
public override void Dump(TextWriter writer, string indent)
{
base.Dump(writer, indent);
writer.WriteLine(indent + " Data Length: " + DataLength);
writer.WriteLine(indent + " Indexed: " + _indexedFlag);
}
protected override void Read(byte[] buffer, int offset, out int length)
{
base.Read(buffer, offset, out length);
uint dataLength = Utilities.ToUInt32LittleEndian(buffer, offset + 0x10);
ushort dataOffset = Utilities.ToUInt16LittleEndian(buffer, offset + 0x14);
_indexedFlag = buffer[offset + 0x16];
if (dataOffset + dataLength > length)
{
throw new IOException("Corrupt attribute, data outside of attribute");
}
_memoryBuffer = new SparseMemoryBuffer(1024);
_memoryBuffer.Write(0, buffer, offset + dataOffset, (int)dataLength);
}
}
}
+177
View File
@@ -0,0 +1,177 @@
//
// 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.IO;
using System.Security.AccessControl;
using System.Security.Principal;
internal sealed class SecurityDescriptor : IByteArraySerializable, IDiagnosticTraceable
{
private RawSecurityDescriptor _securityDescriptor;
public SecurityDescriptor()
{
}
public SecurityDescriptor(RawSecurityDescriptor secDesc)
{
_securityDescriptor = secDesc;
}
public RawSecurityDescriptor Descriptor
{
get { return _securityDescriptor; }
set { _securityDescriptor = value; }
}
public int Size
{
get
{
return _securityDescriptor.BinaryLength;
}
}
public uint CalcHash()
{
byte[] buffer = new byte[Size];
WriteTo(buffer, 0);
uint hash = 0;
for (int i = 0; i < buffer.Length / 4; ++i)
{
hash = Utilities.ToUInt32LittleEndian(buffer, i * 4) + ((hash << 3) | (hash >> 29));
}
return hash;
}
public int ReadFrom(byte[] buffer, int offset)
{
_securityDescriptor = new RawSecurityDescriptor(buffer, offset);
return _securityDescriptor.BinaryLength;
}
public void WriteTo(byte[] buffer, int offset)
{
// Write out the security descriptor manually because on NTFS the DACL is written
// before the Owner & Group. Writing the components in the same order means the
// hashes will match for identical Security Descriptors.
ControlFlags controlFlags = _securityDescriptor.ControlFlags;
buffer[offset + 0x00] = 1;
buffer[offset + 0x01] = _securityDescriptor.ResourceManagerControl;
Utilities.WriteBytesLittleEndian((ushort)controlFlags, buffer, offset + 0x02);
// Blank out offsets, will fill later
for (int i = 0x04; i < 0x14; ++i)
{
buffer[offset + i] = 0;
}
int pos = 0x14;
RawAcl discAcl = _securityDescriptor.DiscretionaryAcl;
if ((controlFlags & ControlFlags.DiscretionaryAclPresent) != 0 && discAcl != null)
{
Utilities.WriteBytesLittleEndian(pos, buffer, offset + 0x10);
discAcl.GetBinaryForm(buffer, offset + pos);
pos += _securityDescriptor.DiscretionaryAcl.BinaryLength;
}
else
{
Utilities.WriteBytesLittleEndian((int)0, buffer, offset + 0x10);
}
RawAcl sysAcl = _securityDescriptor.SystemAcl;
if ((controlFlags & ControlFlags.SystemAclPresent) != 0 && sysAcl != null)
{
Utilities.WriteBytesLittleEndian(pos, buffer, offset + 0x0C);
sysAcl.GetBinaryForm(buffer, offset + pos);
pos += _securityDescriptor.SystemAcl.BinaryLength;
}
else
{
Utilities.WriteBytesLittleEndian((int)0, buffer, offset + 0x0C);
}
Utilities.WriteBytesLittleEndian(pos, buffer, offset + 0x04);
_securityDescriptor.Owner.GetBinaryForm(buffer, offset + pos);
pos += _securityDescriptor.Owner.BinaryLength;
Utilities.WriteBytesLittleEndian(pos, buffer, offset + 0x08);
_securityDescriptor.Group.GetBinaryForm(buffer, offset + pos);
pos += _securityDescriptor.Group.BinaryLength;
if (pos != _securityDescriptor.BinaryLength)
{
throw new IOException("Failed to write Security Descriptor correctly");
}
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "Descriptor: " + _securityDescriptor.GetSddlForm(AccessControlSections.All));
}
internal static RawSecurityDescriptor CalcNewObjectDescriptor(RawSecurityDescriptor parent, bool isContainer)
{
RawAcl sacl = InheritAcl(parent.SystemAcl, isContainer);
RawAcl dacl = InheritAcl(parent.DiscretionaryAcl, isContainer);
return new RawSecurityDescriptor(parent.ControlFlags, parent.Owner, parent.Group, sacl, dacl);
}
private static RawAcl InheritAcl(RawAcl parentAcl, bool isContainer)
{
AceFlags inheritTest = isContainer ? AceFlags.ContainerInherit : AceFlags.ObjectInherit;
RawAcl newAcl = null;
if (parentAcl != null)
{
newAcl = new RawAcl(parentAcl.Revision, parentAcl.Count);
foreach (GenericAce ace in parentAcl)
{
if ((ace.AceFlags & inheritTest) != 0)
{
GenericAce newAce = ace.Copy();
AceFlags newFlags = ace.AceFlags;
if ((newFlags & AceFlags.NoPropagateInherit) != 0)
{
newFlags &= ~(AceFlags.ContainerInherit | AceFlags.ObjectInherit | AceFlags.NoPropagateInherit);
}
newFlags &= ~AceFlags.InheritOnly;
newFlags |= AceFlags.Inherited;
newAce.AceFlags = newFlags;
newAcl.InsertAce(newAcl.Count, newAce);
}
}
}
return newAcl;
}
}
}
@@ -0,0 +1,77 @@
//
// 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;
internal sealed class SecurityDescriptorRecord : IByteArraySerializable
{
public uint Hash;
public uint Id;
public long OffsetInFile;
public uint EntrySize;
public byte[] SecurityDescriptor;
public int Size
{
get { return SecurityDescriptor.Length + 0x14; }
}
public bool Read(byte[] buffer, int offset)
{
Hash = Utilities.ToUInt32LittleEndian(buffer, offset + 0x00);
Id = Utilities.ToUInt32LittleEndian(buffer, offset + 0x04);
OffsetInFile = Utilities.ToInt64LittleEndian(buffer, offset + 0x08);
EntrySize = Utilities.ToUInt32LittleEndian(buffer, offset + 0x10);
if (EntrySize > 0)
{
SecurityDescriptor = new byte[EntrySize - 0x14];
Array.Copy(buffer, offset + 0x14, SecurityDescriptor, 0, SecurityDescriptor.Length);
return true;
}
else
{
return false;
}
}
public int ReadFrom(byte[] buffer, int offset)
{
Read(buffer, offset);
return SecurityDescriptor.Length + 0x14;
}
public void WriteTo(byte[] buffer, int offset)
{
EntrySize = (uint)Size;
Utilities.WriteBytesLittleEndian(Hash, buffer, offset + 0x00);
Utilities.WriteBytesLittleEndian(Id, buffer, offset + 0x04);
Utilities.WriteBytesLittleEndian(OffsetInFile, buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian(EntrySize, buffer, offset + 0x10);
Array.Copy(SecurityDescriptor, 0, buffer, offset + 0x14, SecurityDescriptor.Length);
}
}
}
+383
View File
@@ -0,0 +1,383 @@
//
// 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.Globalization;
using System.IO;
using System.Security.AccessControl;
internal sealed class SecurityDescriptors : IDiagnosticTraceable
{
// File consists of pairs of duplicate blocks (one after the other), providing
// redundancy. When a pair is full, the next pair is used.
private const int BlockSize = 0x40000;
private File _file;
private IndexView<HashIndexKey, HashIndexData> _hashIndex;
private IndexView<IdIndexKey, IdIndexData> _idIndex;
private uint _nextId;
private long _nextSpace;
public SecurityDescriptors(File file)
{
_file = file;
_hashIndex = new IndexView<HashIndexKey, HashIndexData>(file.GetIndex("$SDH"));
_idIndex = new IndexView<IdIndexKey, IdIndexData>(file.GetIndex("$SII"));
foreach (var entry in _idIndex.Entries)
{
if (entry.Key.Id > _nextId)
{
_nextId = entry.Key.Id;
}
long end = entry.Value.SdsOffset + entry.Value.SdsLength;
if (end > _nextSpace)
{
_nextSpace = end;
}
}
if (_nextId == 0)
{
_nextId = 256;
}
else
{
_nextId++;
}
_nextSpace = Utilities.RoundUp(_nextSpace, 16);
}
public static SecurityDescriptors Initialize(File file)
{
file.CreateIndex("$SDH", (AttributeType)0, AttributeCollationRule.SecurityHash);
file.CreateIndex("$SII", (AttributeType)0, AttributeCollationRule.UnsignedLong);
file.CreateStream(AttributeType.Data, "$SDS");
return new SecurityDescriptors(file);
}
public RawSecurityDescriptor GetDescriptorById(uint id)
{
IdIndexData data;
if (_idIndex.TryGetValue(new IdIndexKey(id), out data))
{
return ReadDescriptor(data).Descriptor;
}
return null;
}
public uint AddDescriptor(RawSecurityDescriptor newDescriptor)
{
// Search to see if this is a known descriptor
SecurityDescriptor newDescObj = new SecurityDescriptor(newDescriptor);
uint newHash = newDescObj.CalcHash();
byte[] newByteForm = new byte[newDescObj.Size];
newDescObj.WriteTo(newByteForm, 0);
foreach (var entry in _hashIndex.FindAll(new HashFinder(newHash)))
{
SecurityDescriptor stored = ReadDescriptor(entry.Value);
byte[] storedByteForm = new byte[stored.Size];
stored.WriteTo(storedByteForm, 0);
if (Utilities.AreEqual(newByteForm, storedByteForm))
{
return entry.Value.Id;
}
}
long offset = _nextSpace;
// Write the new descriptor to the end of the existing descriptors
SecurityDescriptorRecord record = new SecurityDescriptorRecord();
record.SecurityDescriptor = newByteForm;
record.Hash = newHash;
record.Id = _nextId;
// If we'd overflow into our duplicate block, skip over it to the
// start of the next block
if (((offset + record.Size) / BlockSize) % 2 == 1)
{
_nextSpace = Utilities.RoundUp(offset, BlockSize * 2);
offset = _nextSpace;
}
record.OffsetInFile = offset;
byte[] buffer = new byte[record.Size];
record.WriteTo(buffer, 0);
using (Stream s = _file.OpenStream(AttributeType.Data, "$SDS", FileAccess.ReadWrite))
{
s.Position = _nextSpace;
s.Write(buffer, 0, buffer.Length);
s.Position = BlockSize + _nextSpace;
s.Write(buffer, 0, buffer.Length);
}
// Make the next descriptor land at the end of this one
_nextSpace = Utilities.RoundUp(_nextSpace + buffer.Length, 16);
_nextId++;
// Update the indexes
HashIndexData hashIndexData = new HashIndexData();
hashIndexData.Hash = record.Hash;
hashIndexData.Id = record.Id;
hashIndexData.SdsOffset = record.OffsetInFile;
hashIndexData.SdsLength = (int)record.EntrySize;
HashIndexKey hashIndexKey = new HashIndexKey();
hashIndexKey.Hash = record.Hash;
hashIndexKey.Id = record.Id;
_hashIndex[hashIndexKey] = hashIndexData;
IdIndexData idIndexData = new IdIndexData();
idIndexData.Hash = record.Hash;
idIndexData.Id = record.Id;
idIndexData.SdsOffset = record.OffsetInFile;
idIndexData.SdsLength = (int)record.EntrySize;
IdIndexKey idIndexKey = new IdIndexKey();
idIndexKey.Id = record.Id;
_idIndex[idIndexKey] = idIndexData;
_file.UpdateRecordInMft();
return record.Id;
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + "SECURITY DESCRIPTORS");
using (Stream s = _file.OpenStream(AttributeType.Data, "$SDS", FileAccess.Read))
{
byte[] buffer = Utilities.ReadFully(s, (int)s.Length);
foreach (var entry in _idIndex.Entries)
{
int pos = (int)entry.Value.SdsOffset;
SecurityDescriptorRecord rec = new SecurityDescriptorRecord();
if (!rec.Read(buffer, pos))
{
break;
}
string secDescStr = "--unknown--";
if (rec.SecurityDescriptor[0] != 0)
{
RawSecurityDescriptor sd = new RawSecurityDescriptor(rec.SecurityDescriptor, 0);
secDescStr = sd.GetSddlForm(AccessControlSections.All);
}
writer.WriteLine(indent + " SECURITY DESCRIPTOR RECORD");
writer.WriteLine(indent + " Hash: " + rec.Hash);
writer.WriteLine(indent + " Id: " + rec.Id);
writer.WriteLine(indent + " File Offset: " + rec.OffsetInFile);
writer.WriteLine(indent + " Size: " + rec.EntrySize);
writer.WriteLine(indent + " Value: " + secDescStr);
}
}
}
private SecurityDescriptor ReadDescriptor(IndexData data)
{
using (Stream s = _file.OpenStream(AttributeType.Data, "$SDS", FileAccess.Read))
{
s.Position = data.SdsOffset;
byte[] buffer = Utilities.ReadFully(s, data.SdsLength);
SecurityDescriptorRecord record = new SecurityDescriptorRecord();
record.Read(buffer, 0);
return new SecurityDescriptor(new RawSecurityDescriptor(record.SecurityDescriptor, 0));
}
}
internal abstract class IndexData
{
public uint Hash;
public uint Id;
public long SdsOffset;
public int SdsLength;
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[Data-Hash:{0},Id:{1},SdsOffset:{2},SdsLength:{3}]", Hash, Id, SdsOffset, SdsLength);
}
}
internal sealed class HashIndexKey : IByteArraySerializable
{
public uint Hash;
public uint Id;
public int Size
{
get { return 8; }
}
public int ReadFrom(byte[] buffer, int offset)
{
Hash = Utilities.ToUInt32LittleEndian(buffer, offset + 0);
Id = Utilities.ToUInt32LittleEndian(buffer, offset + 4);
return 8;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(Hash, buffer, offset + 0);
Utilities.WriteBytesLittleEndian(Id, buffer, offset + 4);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[Key-Hash:{0},Id:{1}]", Hash, Id);
}
}
internal sealed class HashIndexData : IndexData, IByteArraySerializable
{
public int Size
{
get { return 0x14; }
}
public int ReadFrom(byte[] buffer, int offset)
{
Hash = Utilities.ToUInt32LittleEndian(buffer, offset + 0x00);
Id = Utilities.ToUInt32LittleEndian(buffer, offset + 0x04);
SdsOffset = Utilities.ToInt64LittleEndian(buffer, offset + 0x08);
SdsLength = Utilities.ToInt32LittleEndian(buffer, offset + 0x10);
return 0x14;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(Hash, buffer, offset + 0x00);
Utilities.WriteBytesLittleEndian(Id, buffer, offset + 0x04);
Utilities.WriteBytesLittleEndian(SdsOffset, buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian(SdsLength, buffer, offset + 0x10);
////Array.Copy(new byte[] { (byte)'I', 0, (byte)'I', 0 }, 0, buffer, offset + 0x14, 4);
}
}
internal sealed class IdIndexKey : IByteArraySerializable
{
public uint Id;
public IdIndexKey()
{
}
public IdIndexKey(uint id)
{
Id = id;
}
public int Size
{
get { return 4; }
}
public int ReadFrom(byte[] buffer, int offset)
{
Id = Utilities.ToUInt32LittleEndian(buffer, offset + 0);
return 4;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(Id, buffer, offset + 0);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[Key-Id:{0}]", Id);
}
}
internal sealed class IdIndexData : IndexData, IByteArraySerializable
{
public int Size
{
get { return 0x14; }
}
public int ReadFrom(byte[] buffer, int offset)
{
Hash = Utilities.ToUInt32LittleEndian(buffer, offset + 0x00);
Id = Utilities.ToUInt32LittleEndian(buffer, offset + 0x04);
SdsOffset = Utilities.ToInt64LittleEndian(buffer, offset + 0x08);
SdsLength = Utilities.ToInt32LittleEndian(buffer, offset + 0x10);
return 0x14;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(Hash, buffer, offset + 0x00);
Utilities.WriteBytesLittleEndian(Id, buffer, offset + 0x04);
Utilities.WriteBytesLittleEndian(SdsOffset, buffer, offset + 0x08);
Utilities.WriteBytesLittleEndian(SdsLength, buffer, offset + 0x10);
}
}
private class HashFinder : IComparable<HashIndexKey>
{
private uint _toMatch;
public HashFinder(uint toMatch)
{
_toMatch = toMatch;
}
public int CompareTo(uint otherHash)
{
if (_toMatch < otherHash)
{
return -1;
}
else if (_toMatch > otherHash)
{
return 1;
}
return 0;
}
public int CompareTo(HashIndexKey other)
{
return CompareTo(other.Hash);
}
}
}
}
+49
View File
@@ -0,0 +1,49 @@
//
// 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.Text;
/// <summary>
/// Controls whether short file names are created automatically.
/// </summary>
public enum ShortFileNameOption
{
/// <summary>
/// Creates short file names, unless they've been disabled in NTFS.
/// </summary>
UseVolumeFlag,
/// <summary>
/// Does not create short names, ignoring the NTFS setting.
/// </summary>
Disabled,
/// <summary>
/// Always creates short names, ignoring the NTFS setting.
/// </summary>
Enabled
}
}
+91
View File
@@ -0,0 +1,91 @@
//
// 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.Collections.Generic;
internal sealed class SparseClusterStream : ClusterStream
{
private NtfsAttribute _attr;
private RawClusterStream _rawStream;
public SparseClusterStream(NtfsAttribute attr, RawClusterStream rawStream)
{
_attr = attr;
_rawStream = rawStream;
}
public override long AllocatedClusterCount
{
get { return _rawStream.AllocatedClusterCount; }
}
public override IEnumerable<Range<long, long>> StoredClusters
{
get { return _rawStream.StoredClusters; }
}
public override bool IsClusterStored(long vcn)
{
return _rawStream.IsClusterStored(vcn);
}
public override void ExpandToClusters(long numVirtualClusters, NonResidentAttributeRecord extent, bool allocate)
{
_rawStream.ExpandToClusters(CompressionStart(numVirtualClusters), extent, false);
}
public override void TruncateToClusters(long numVirtualClusters)
{
long alignedNum = CompressionStart(numVirtualClusters);
_rawStream.TruncateToClusters(alignedNum);
if (alignedNum != numVirtualClusters)
{
_rawStream.ReleaseClusters(numVirtualClusters, (int)(alignedNum - numVirtualClusters));
}
}
public override void ReadClusters(long startVcn, int count, byte[] buffer, int offset)
{
_rawStream.ReadClusters(startVcn, count, buffer, offset);
}
public override int WriteClusters(long startVcn, int count, byte[] buffer, int offset)
{
int clustersAllocated = 0;
clustersAllocated += _rawStream.AllocateClusters(startVcn, count);
clustersAllocated += _rawStream.WriteClusters(startVcn, count, buffer, offset);
return clustersAllocated;
}
public override int ClearClusters(long startVcn, int count)
{
return _rawStream.ReleaseClusters(startVcn, count);
}
private long CompressionStart(long vcn)
{
return Utilities.RoundUp(vcn, _attr.CompressionUnitSize);
}
}
}
+157
View File
@@ -0,0 +1,157 @@
//
// 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.IO;
internal sealed class StandardInformation : IByteArraySerializable, IDiagnosticTraceable
{
public DateTime CreationTime;
public DateTime ModificationTime;
public DateTime MftChangedTime;
public DateTime LastAccessTime;
public FileAttributeFlags FileAttributes;
public uint MaxVersions;
public uint Version;
public uint ClassId;
public uint OwnerId;
public uint SecurityId;
public ulong QuotaCharged;
public ulong UpdateSequenceNumber;
private bool _haveExtraFields = true;
public int Size
{
get { return _haveExtraFields ? 0x48 : 0x30; }
}
public static StandardInformation InitializeNewFile(File file, FileAttributeFlags flags)
{
DateTime now = DateTime.UtcNow;
NtfsStream siStream = file.CreateStream(AttributeType.StandardInformation, null);
StandardInformation si = new StandardInformation();
si.CreationTime = now;
si.ModificationTime = now;
si.MftChangedTime = now;
si.LastAccessTime = now;
si.FileAttributes = flags;
siStream.SetContent(si);
return si;
}
public int ReadFrom(byte[] buffer, int offset)
{
CreationTime = ReadDateTime(buffer, 0x00);
ModificationTime = ReadDateTime(buffer, 0x08);
MftChangedTime = ReadDateTime(buffer, 0x10);
LastAccessTime = ReadDateTime(buffer, 0x18);
FileAttributes = (FileAttributeFlags)Utilities.ToUInt32LittleEndian(buffer, 0x20);
MaxVersions = Utilities.ToUInt32LittleEndian(buffer, 0x24);
Version = Utilities.ToUInt32LittleEndian(buffer, 0x28);
ClassId = Utilities.ToUInt32LittleEndian(buffer, 0x2C);
if (buffer.Length > 0x30)
{
OwnerId = Utilities.ToUInt32LittleEndian(buffer, 0x30);
SecurityId = Utilities.ToUInt32LittleEndian(buffer, 0x34);
QuotaCharged = Utilities.ToUInt64LittleEndian(buffer, 0x38);
UpdateSequenceNumber = Utilities.ToUInt64LittleEndian(buffer, 0x40);
_haveExtraFields = true;
return 0x48;
}
else
{
_haveExtraFields = false;
return 0x30;
}
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian(CreationTime.ToFileTimeUtc(), buffer, 0x00);
Utilities.WriteBytesLittleEndian(ModificationTime.ToFileTimeUtc(), buffer, 0x08);
Utilities.WriteBytesLittleEndian(MftChangedTime.ToFileTimeUtc(), buffer, 0x10);
Utilities.WriteBytesLittleEndian(LastAccessTime.ToFileTimeUtc(), buffer, 0x18);
Utilities.WriteBytesLittleEndian((uint)FileAttributes, buffer, 0x20);
Utilities.WriteBytesLittleEndian(MaxVersions, buffer, 0x24);
Utilities.WriteBytesLittleEndian(Version, buffer, 0x28);
Utilities.WriteBytesLittleEndian(ClassId, buffer, 0x2C);
if (_haveExtraFields)
{
Utilities.WriteBytesLittleEndian(OwnerId, buffer, 0x30);
Utilities.WriteBytesLittleEndian(SecurityId, buffer, 0x34);
Utilities.WriteBytesLittleEndian(QuotaCharged, buffer, 0x38);
Utilities.WriteBytesLittleEndian(UpdateSequenceNumber, buffer, 0x38);
}
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + " Creation Time: " + CreationTime);
writer.WriteLine(indent + " Modification Time: " + ModificationTime);
writer.WriteLine(indent + " MFT Changed Time: " + MftChangedTime);
writer.WriteLine(indent + " Last Access Time: " + LastAccessTime);
writer.WriteLine(indent + " File Permissions: " + FileAttributes);
writer.WriteLine(indent + " Max Versions: " + MaxVersions);
writer.WriteLine(indent + " Version: " + Version);
writer.WriteLine(indent + " Class Id: " + ClassId);
writer.WriteLine(indent + " Security Id: " + SecurityId);
writer.WriteLine(indent + " Quota Charged: " + QuotaCharged);
writer.WriteLine(indent + " Update Seq Num: " + UpdateSequenceNumber);
}
internal static FileAttributes ConvertFlags(FileAttributeFlags flags, bool isDirectory)
{
FileAttributes result = (FileAttributes)(((uint)flags) & 0xFFFF);
if (isDirectory)
{
result |= System.IO.FileAttributes.Directory;
}
return result;
}
internal static FileAttributeFlags SetFileAttributes(FileAttributes newAttributes, FileAttributeFlags existing)
{
return (FileAttributeFlags)(((uint)existing & 0xFFFF0000) | ((uint)newAttributes & 0xFFFF));
}
private static DateTime ReadDateTime(byte[] buffer, int offset)
{
try
{
return DateTime.FromFileTimeUtc(Utilities.ToInt64LittleEndian(buffer, offset));
}
catch (ArgumentException)
{
return DateTime.FromFileTimeUtc(0);
}
}
}
}
+105
View File
@@ -0,0 +1,105 @@
//
// 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.IO;
internal class StructuredNtfsAttribute<T> : NtfsAttribute
where T : IByteArraySerializable, IDiagnosticTraceable, new()
{
private T _structure;
private bool _initialized;
private bool _hasContent;
public StructuredNtfsAttribute(File file, FileRecordReference containingFile, AttributeRecord record)
: base(file, containingFile, record)
{
_structure = new T();
}
public T Content
{
get
{
Initialize();
return _structure;
}
set
{
_structure = value;
_hasContent = true;
}
}
public bool HasContent
{
get
{
Initialize();
return _hasContent;
}
}
public void Save()
{
byte[] buffer = new byte[_structure.Size];
_structure.WriteTo(buffer, 0);
using (Stream s = Open(FileAccess.Write))
{
s.Write(buffer, 0, buffer.Length);
s.SetLength(buffer.Length);
}
}
public override string ToString()
{
Initialize();
return _structure.ToString();
}
public override void Dump(TextWriter writer, string indent)
{
Initialize();
writer.WriteLine(indent + AttributeTypeName + " ATTRIBUTE (" + (Name == null ? "No Name" : Name) + ")");
_structure.Dump(writer, indent + " ");
_primaryRecord.Dump(writer, indent + " ");
}
private void Initialize()
{
if (!_initialized)
{
using (Stream s = Open(FileAccess.Read))
{
byte[] buffer = Utilities.ReadFully(s, (int)Length);
_structure.ReadFrom(buffer, 0);
_hasContent = s.Length != 0;
}
_initialized = true;
}
}
}
}
+101
View File
@@ -0,0 +1,101 @@
//
// 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.IO;
internal sealed class UpperCase : IComparer<string>
{
private char[] _table;
public UpperCase(File file)
{
using (Stream s = file.OpenStream(AttributeType.Data, null, FileAccess.Read))
{
_table = new char[s.Length / 2];
byte[] buffer = Utilities.ReadFully(s, (int)s.Length);
for (int i = 0; i < _table.Length; ++i)
{
_table[i] = (char)Utilities.ToUInt16LittleEndian(buffer, i * 2);
}
}
}
public int Compare(string x, string y)
{
int compLen = Math.Min(x.Length, y.Length);
for (int i = 0; i < compLen; ++i)
{
int result = _table[x[i]] - _table[y[i]];
if (result != 0)
{
return result;
}
}
// Identical out to the shortest string, so length is now the
// determining factor.
return x.Length - y.Length;
}
public int Compare(byte[] x, int xOffset, int xLength, byte[] y, int yOffset, int yLength)
{
int compLen = Math.Min(xLength, yLength) / 2;
for (int i = 0; i < compLen; ++i)
{
char xCh = (char)(x[xOffset + (i * 2)] | (x[xOffset + ((i * 2) + 1)] << 8));
char yCh = (char)(y[yOffset + (i * 2)] | (y[yOffset + ((i * 2) + 1)] << 8));
int result = _table[xCh] - _table[yCh];
if (result != 0)
{
return result;
}
}
// Identical out to the shortest string, so length is now the
// determining factor.
return xLength - yLength;
}
internal static UpperCase Initialize(File file)
{
byte[] buffer = new byte[(char.MaxValue + 1) * 2];
for (int i = Char.MinValue; i <= char.MaxValue; ++i)
{
Utilities.WriteBytesLittleEndian((ushort)char.ToUpperInvariant((char)i), buffer, i * 2);
}
using (Stream s = file.OpenStream(AttributeType.Data, null, FileAccess.ReadWrite))
{
s.Write(buffer, 0, buffer.Length);
}
return new UpperCase(file);
}
}
}
+86
View File
@@ -0,0 +1,86 @@
//
// 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.IO;
internal sealed class VolumeInformation : IByteArraySerializable, IDiagnosticTraceable
{
public const int VersionNt4 = 0x0102;
public const int VersionW2k = 0x0300;
public const int VersionXp = 0x0301;
private byte _majorVersion;
private byte _minorVersion;
private VolumeInformationFlags _flags;
public VolumeInformation()
{
}
public VolumeInformation(byte major, byte minor, VolumeInformationFlags flags)
{
_majorVersion = major;
_minorVersion = minor;
_flags = flags;
}
public VolumeInformationFlags Flags
{
get { return _flags; }
}
public int Version
{
get { return ((int)_majorVersion) << 8 | _minorVersion; }
}
public int Size
{
get { return 0x0C; }
}
public int ReadFrom(byte[] buffer, int offset)
{
_majorVersion = buffer[offset + 0x08];
_minorVersion = buffer[offset + 0x09];
_flags = (VolumeInformationFlags)Utilities.ToUInt16LittleEndian(buffer, offset + 0x0A);
return 0x0C;
}
public void WriteTo(byte[] buffer, int offset)
{
Utilities.WriteBytesLittleEndian((ulong)0, buffer, offset + 0x00);
buffer[offset + 0x08] = _majorVersion;
buffer[offset + 0x09] = _minorVersion;
Utilities.WriteBytesLittleEndian((ushort)_flags, buffer, offset + 0x0A);
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + " Version: " + _majorVersion + "." + _minorVersion);
writer.WriteLine(indent + " Flags: " + _flags);
}
}
}
+40
View File
@@ -0,0 +1,40 @@
//
// 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;
[Flags]
internal enum VolumeInformationFlags : ushort
{
None = 0x00,
Dirty = 0x01,
ResizeLogFile = 0x02,
UpgradeOnMount = 0x04,
MountedOnNT4 = 0x08,
DeleteUSNUnderway = 0x10,
RepairObjectIds = 0x20,
DisableShortNameCreation = 0x80,
ModifiedByChkDsk = 0x8000
}
}
+68
View File
@@ -0,0 +1,68 @@
//
// 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.IO;
using System.Text;
internal sealed class VolumeName : IByteArraySerializable, IDiagnosticTraceable
{
private string _name;
public VolumeName()
{
}
public VolumeName(string name)
{
_name = name;
}
public string Name
{
get { return _name; }
}
public int Size
{
get { return Encoding.Unicode.GetByteCount(_name); }
}
public int ReadFrom(byte[] buffer, int offset)
{
_name = Encoding.Unicode.GetString(buffer, offset, buffer.Length - offset);
return buffer.Length - offset;
}
public void WriteTo(byte[] buffer, int offset)
{
Encoding.Unicode.GetBytes(_name, 0, _name.Length, buffer, offset);
}
public void Dump(TextWriter writer, string indent)
{
writer.WriteLine(indent + " Volume Name: " + _name);
}
}
}