// // 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 _runs; private int _firstDirty = int.MaxValue; private int _lastDirty = 0; public CookedDataRuns() { _runs = new List(); } public CookedDataRuns(IEnumerable rawRuns, NonResidentAttributeRecord attributeExtent) { _runs = new List(); 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 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 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; } } } /// /// Truncates the set of data runs. /// /// The first run to be truncated. 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; } } }