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