Files
WPinternals/DiscUtils/Ntfs/RawClusterStream.cs
T
2018-10-25 22:35:49 +02:00

369 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.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;
}
}
}