mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-06-15 20:00:12 +10:00
354 lines
13 KiB
C#
354 lines
13 KiB
C#
//
|
|
// Copyright (c) 2008-2011, Kenneth Bell
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
// to deal in the Software without restriction, including without limitation
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
namespace DiscUtils.Ntfs
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.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();
|
|
}
|
|
}
|
|
}
|