// // 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; /// /// Low-level non-resident attribute operations. /// /// /// 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. /// 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> StoredClusters { get { Range lastVcnRange = null; List> ranges = new List>(); 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(lastVcnRange.Offset, lastVcnRange.Count + cookedRun.Length); ranges[ranges.Count - 1] = lastVcnRange; } else { lastVcnRange = new Range(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 runs = new List(); 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(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; } } }