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