//
// 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;
}
}
///
/// Allocates clusters from the disk.
///
/// The number of clusters to allocate.
/// The proposed start cluster (or -1).
/// true if this attribute is the $MFT\$DATA attribute.
/// The total number of clusters in the file, including this allocation.
/// The list of cluster allocations.
public Tuple[] AllocateClusters(long count, long proposedStart, bool isMft, long total)
{
List> result = new List>();
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[] runs)
{
foreach (var run in runs)
{
_bitmap.MarkAbsentRange(run.First, run.Second);
}
}
internal void FreeClusters(params Range[] runs)
{
foreach (var run in runs)
{
_bitmap.MarkAbsentRange(run.Offset, run.Count);
}
}
///
/// Sets the total number of clusters managed in the volume.
///
/// Total number of clusters in the volume.
///
/// Any clusters represented in the bitmap beyond the total number in the volume are marked as in-use.
///
internal void SetTotalClusters(long numClusters)
{
long actualClusters = _bitmap.SetTotalEntries(numClusters);
if (actualClusters != numClusters)
{
MarkAllocated(numClusters, actualClusters - numClusters);
}
}
private long ExtendRun(long count, List> 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(start, numFound));
}
return numFound;
}
///
/// Finds one or more free clusters in a range.
///
/// The number of clusters required.
/// The list of clusters found (i.e. out param).
/// The first cluster in the range to look at.
/// The last cluster in the range to look at (exclusive).
/// Indicates if the clusters are for the MFT.
/// Indicates if contiguous clusters are required.
/// Indicates how many clusters to skip before next allocation, to prevent fragmentation.
/// The number of clusters found in the range.
private long FindClusters(long count, List> 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(runStart, focusCluster - runStart));
numFound += focusCluster - runStart;
}
}
else
{
++focusCluster;
}
++numInspected;
if (focusCluster >= end)
{
focusCluster = start;
}
}
if (!isMft)
{
_nextDataCluster = focusCluster + headroom;
}
return numFound;
}
}
}