mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-08-10 10:01:14 +10:00
Initial commit - WPinternals 2.6
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user