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

271 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.Collections.Generic;
using System.IO;
internal class ClusterBitmap : System.IDisposable
{
private File _file;
private Bitmap _bitmap;
private long _nextDataCluster;
private bool _fragmentedDiskMode;
public ClusterBitmap(File file)
{
_file = file;
_bitmap = new Bitmap(
_file.OpenStream(AttributeType.Data, null, FileAccess.ReadWrite),
Utilities.Ceil(file.Context.BiosParameterBlock.TotalSectors64, file.Context.BiosParameterBlock.SectorsPerCluster));
}
public void Dispose()
{
if (_bitmap != null)
{
_bitmap.Dispose();
_bitmap = null;
}
}
/// <summary>
/// Allocates clusters from the disk.
/// </summary>
/// <param name="count">The number of clusters to allocate.</param>
/// <param name="proposedStart">The proposed start cluster (or -1).</param>
/// <param name="isMft"><c>true</c> if this attribute is the $MFT\$DATA attribute.</param>
/// <param name="total">The total number of clusters in the file, including this allocation.</param>
/// <returns>The list of cluster allocations.</returns>
public Tuple<long, long>[] AllocateClusters(long count, long proposedStart, bool isMft, long total)
{
List<Tuple<long, long>> result = new List<Tuple<long, long>>();
long numFound = 0;
long totalClusters = _file.Context.RawStream.Length / _file.Context.BiosParameterBlock.BytesPerCluster;
if (isMft)
{
// First, try to extend the existing cluster run (if available)
if (proposedStart >= 0)
{
numFound += ExtendRun(count - numFound, result, proposedStart, totalClusters);
}
// The MFT grows sequentially across the disk
if (numFound < count && !_fragmentedDiskMode)
{
numFound += FindClusters(count - numFound, result, 0, totalClusters, isMft, true, 0);
}
if (numFound < count)
{
numFound += FindClusters(count - numFound, result, 0, totalClusters, isMft, false, 0);
}
}
else
{
// First, try to extend the existing cluster run (if available)
if (proposedStart >= 0)
{
numFound += ExtendRun(count - numFound, result, proposedStart, totalClusters);
}
// Try to find a contiguous range
if (numFound < count && !_fragmentedDiskMode)
{
numFound += FindClusters(count - numFound, result, totalClusters / 8, totalClusters, isMft, true, total / 4);
}
if (numFound < count)
{
numFound += FindClusters(count - numFound, result, totalClusters / 8, totalClusters, isMft, false, 0);
}
if (numFound < count)
{
numFound = FindClusters(count - numFound, result, totalClusters / 16, totalClusters / 8, isMft, false, 0);
}
if (numFound < count)
{
numFound = FindClusters(count - numFound, result, totalClusters / 32, totalClusters / 16, isMft, false, 0);
}
if (numFound < count)
{
numFound = FindClusters(count - numFound, result, 0, totalClusters / 32, isMft, false, 0);
}
}
if (numFound < count)
{
FreeClusters(result.ToArray());
throw new IOException("Out of disk space");
}
// If we found more than two clusters, or we have a fragmented result,
// then switch out of trying to allocate contiguous ranges. Similarly,
// switch back if we found a resonable quantity in a single span.
if ((numFound > 4 && result.Count == 1) || result.Count > 1)
{
_fragmentedDiskMode = (numFound / result.Count) < 4;
}
return result.ToArray();
}
internal void MarkAllocated(long first, long count)
{
_bitmap.MarkPresentRange(first, count);
}
internal void FreeClusters(params Tuple<long, long>[] runs)
{
foreach (var run in runs)
{
_bitmap.MarkAbsentRange(run.First, run.Second);
}
}
internal void FreeClusters(params Range<long, long>[] runs)
{
foreach (var run in runs)
{
_bitmap.MarkAbsentRange(run.Offset, run.Count);
}
}
/// <summary>
/// Sets the total number of clusters managed in the volume.
/// </summary>
/// <param name="numClusters">Total number of clusters in the volume.</param>
/// <remarks>
/// Any clusters represented in the bitmap beyond the total number in the volume are marked as in-use.
/// </remarks>
internal void SetTotalClusters(long numClusters)
{
long actualClusters = _bitmap.SetTotalEntries(numClusters);
if (actualClusters != numClusters)
{
MarkAllocated(numClusters, actualClusters - numClusters);
}
}
private long ExtendRun(long count, List<Tuple<long, long>> result, long start, long end)
{
long focusCluster = start;
while (!_bitmap.IsPresent(focusCluster) && focusCluster < end && focusCluster - start < count)
{
++focusCluster;
}
long numFound = focusCluster - start;
if (numFound > 0)
{
_bitmap.MarkPresentRange(start, numFound);
result.Add(new Tuple<long, long>(start, numFound));
}
return numFound;
}
/// <summary>
/// Finds one or more free clusters in a range.
/// </summary>
/// <param name="count">The number of clusters required.</param>
/// <param name="result">The list of clusters found (i.e. out param).</param>
/// <param name="start">The first cluster in the range to look at.</param>
/// <param name="end">The last cluster in the range to look at (exclusive).</param>
/// <param name="isMft">Indicates if the clusters are for the MFT.</param>
/// <param name="contiguous">Indicates if contiguous clusters are required.</param>
/// <param name="headroom">Indicates how many clusters to skip before next allocation, to prevent fragmentation.</param>
/// <returns>The number of clusters found in the range.</returns>
private long FindClusters(long count, List<Tuple<long, long>> result, long start, long end, bool isMft, bool contiguous, long headroom)
{
long numFound = 0;
long focusCluster;
if (isMft)
{
focusCluster = start;
}
else
{
if (_nextDataCluster < start || _nextDataCluster >= end)
{
_nextDataCluster = start;
}
focusCluster = _nextDataCluster;
}
long numInspected = 0;
while (numFound < count && focusCluster >= start && numInspected < end - start)
{
if (!_bitmap.IsPresent(focusCluster))
{
// Start of a run...
long runStart = focusCluster;
++focusCluster;
while (!_bitmap.IsPresent(focusCluster) && focusCluster - runStart < (count - numFound))
{
++focusCluster;
++numInspected;
}
if (!contiguous || (focusCluster - runStart) == (count - numFound))
{
_bitmap.MarkPresentRange(runStart, focusCluster - runStart);
result.Add(new Tuple<long, long>(runStart, focusCluster - runStart));
numFound += focusCluster - runStart;
}
}
else
{
++focusCluster;
}
++numInspected;
if (focusCluster >= end)
{
focusCluster = start;
}
}
if (!isMft)
{
_nextDataCluster = focusCluster + headroom;
}
return numFound;
}
}
}