mirror of
https://github.com/ReneLergner/WPinternals.git
synced 2026-06-14 03:16:40 +10:00
258 lines
10 KiB
C#
258 lines
10 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;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|