Skip to content

Commit

Permalink
Atomic bitmap allocator
Browse files Browse the repository at this point in the history
  • Loading branch information
picrap committed Feb 14, 2018
1 parent 66b88db commit c96b3fd
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 72 deletions.
163 changes: 113 additions & 50 deletions ExFat.Core/Partition/ExFatAllocationBitmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class ExFatAllocationBitmap
private Stream _dataStream;
private uint _firstCluster;
private bool _delayWrite;
private readonly object _lock = new object();

/// <summary>
/// Gets the length.
Expand All @@ -27,20 +28,6 @@ public class ExFatAllocationBitmap
/// </value>
public long Length { get; private set; }

/// <summary>
/// Gets or sets the allocation state for the specified cluster.
/// </summary>
/// <value>
/// The <see cref="System.Boolean"/>.
/// </value>
/// <param name="cluster">The cluster.</param>
/// <returns></returns>
public bool this[Cluster cluster]
{
get { return GetAt(cluster); }
set { SetAt(cluster, value); }
}

/// <summary>
/// Opens the specified data stream.
/// </summary>
Expand All @@ -64,8 +51,11 @@ public void Open(Stream dataStream, uint firstCluster, long totalClusters, bool
/// <param name="dataStream">The data stream.</param>
public void Write(Stream dataStream)
{
_dataStream = dataStream;
dataStream.Write(_bitmap, 0, _bitmap.Length);
lock (_lock)
{
_dataStream = dataStream;
dataStream.Write(_bitmap, 0, _bitmap.Length);
}
}

/// <summary>
Expand Down Expand Up @@ -99,55 +89,139 @@ public void Dispose()
/// <exception cref="System.ArgumentOutOfRangeException">cluster</exception>
public bool GetAt(Cluster cluster)
{
if (cluster.Value < _firstCluster || cluster.Value >= Length)
throw new ArgumentOutOfRangeException(nameof(cluster));
var clusterIndex = cluster.Value - _firstCluster;
return GetAtIndex(clusterIndex);
lock (_lock)
{
if (cluster.Value < _firstCluster || cluster.Value >= Length)
throw new ArgumentOutOfRangeException(nameof(cluster));
var clusterIndex = cluster.Value - _firstCluster;
return GetAtIndex(clusterIndex);
}
}

private bool GetAtIndex(long clusterIndex)
{
var byteIndex = (int)clusterIndex / 8;
var bitMask = 1 << (int)(clusterIndex & 7);
return (_bitmap[byteIndex] & bitMask) != 0;
lock (_lock)
{
var byteIndex = (int)clusterIndex / 8;
var bitMask = 1 << (int)(clusterIndex & 7);
return (_bitmap[byteIndex] & bitMask) != 0;
}
}

/// <summary>
/// Allocates or frees the specified cluster
/// Sets the allocation for the given cluster.
/// </summary>
/// <param name="cluster">The cluster.</param>
/// <param name="allocated">if set to <c>true</c> [allocated].</param>
/// <exception cref="System.ArgumentOutOfRangeException">cluster</exception>
public void SetAt(Cluster cluster, bool allocated)
/// <returns></returns>
private int SetAllocation(Cluster cluster, bool allocated)
{
if (cluster.Value < _firstCluster || cluster.Value >= Length)
throw new ArgumentOutOfRangeException(nameof(cluster));
var clusterIndex = cluster.Value - _firstCluster;
var byteIndex = (int)clusterIndex / 8;
var bitMask = 1 << (int)(clusterIndex & 7);
if (allocated)
_bitmap[byteIndex] |= (byte)bitMask;
else
_bitmap[byteIndex] &= (byte)~bitMask;
// update stream only if write is not delayed
return byteIndex;
}

/// <summary>
/// Writes the specified byte(s) to disk.
/// </summary>
/// <param name="byteIndex">Index of the byte.</param>
/// <param name="length">The length.</param>
private void Write(int byteIndex, int length)
{
if (_dataStream != null && !_delayWrite)
{
_dataStream.Seek(byteIndex, SeekOrigin.Begin);
_dataStream.Write(_bitmap, byteIndex, 1);
_dataStream.Write(_bitmap, byteIndex, length);
}
}

/// <summary>
/// Gets the used clusters.
/// This is an optimistic version (since the result may change all the time)
/// </summary>
/// <returns></returns>
public long GetUsedClusters()
{
long usedClusters = 0;
for (int clusterIndex = 0; clusterIndex < Length - _firstCluster;)
{
if (clusterIndex % 8 == 0)
{
if (_bitmap[clusterIndex / 8] == 0xFF)
usedClusters += 8;
clusterIndex += 8;
}
else
{
if (GetAtIndex(clusterIndex++))
usedClusters++;
}
}
return usedClusters;
}

/// <summary>
/// Allocates a cluster.
/// </summary>
/// <param name="contigous">The contigous clusters wanted.</param>
/// <returns></returns>
public Cluster Allocate(int contigous = 1)
{
lock (_lock)
return Allocate(FindAvailable(_firstCluster, contigous), contigous) ?? Cluster.Free;
}

/// <summary>
/// Allocates a cluster, at (or after) the specified cluster.
/// </summary>
/// <param name="hint">The hint.</param>
/// <param name="contigous">The contigous clusters wanted.</param>
/// <returns></returns>
public Cluster Allocate(Cluster hint, int contigous = 1)
{
lock (_lock)
return Allocate(FindAvailable(hint, contigous) ?? FindAvailable(_firstCluster, contigous), contigous) ?? Cluster.Free;
}

private Cluster? Allocate(Cluster? first, int contigous)
{
if (!first.HasValue)
return null;

int? firstByteIndex = null;
int lastByteIndex = 0;
for (int index = 0; index < contigous; index++)
{
lastByteIndex = SetAllocation(first.Value + index, true);
if (!firstByteIndex.HasValue)
firstByteIndex = lastByteIndex;
}

if (firstByteIndex.HasValue)
Write(firstByteIndex.Value, lastByteIndex - firstByteIndex.Value + 1);
return first.Value;
}

/// <summary>
/// Finds one or more unallocated cluster.
/// Does not allocate them, so all allocation process must be perform form within a lock
/// </summary>
/// <param name="first">The first.</param>
/// <param name="contiguous">The contiguous.</param>
/// <returns></returns>
public Cluster FindUnallocated(int contiguous = 1)
private Cluster? FindAvailable(Cluster first, int contiguous = 1)
{
if (!first.IsData)
return null;

UInt32 freeCluster = 0;
int unallocatedCount = 0;
for (UInt32 cluster = _firstCluster; cluster < Length;)
for (UInt32 cluster = first.ToUInt32(); cluster < Length;)
{
// special case: byte is filled, skip the block (and reset the search)
if (((cluster - _firstCluster) & 0x07) == 0 && _bitmap[cluster / 8] == 0xFF)
Expand All @@ -172,31 +246,20 @@ public Cluster FindUnallocated(int contiguous = 1)
++cluster;
}
// nothing found
return Cluster.Free;
return null;
}

/// <summary>
/// Gets the used clusters.
/// Frees the specified cluster.
/// </summary>
/// <returns></returns>
public long GetUsedClusters()
/// <param name="cluster">The cluster.</param>
public void Free(Cluster cluster)
{
long usedClusters = 0;
for (int clusterIndex = 0; clusterIndex < Length - _firstCluster;)
lock (_lock)
{
if (clusterIndex % 8 == 0)
{
if (_bitmap[clusterIndex / 8] == 0xFF)
usedClusters += 8;
clusterIndex += 8;
}
else
{
if (GetAtIndex(clusterIndex++))
usedClusters++;
}
var byteIndex = SetAllocation(cluster, false);
Write(byteIndex, 1);
}
return usedClusters;
}
}
}
38 changes: 16 additions & 22 deletions ExFat.Core/Partition/ExFatPartition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,19 +348,7 @@ public Cluster AllocateCluster(Cluster previousClusterHint)
lock (_allocationLock)
{
var allocationBitmap = GetAllocationBitmap();
Cluster cluster;
// no data? anything else is good
if (!previousClusterHint.IsData)
cluster = allocationBitmap.FindUnallocated();
else
{
// try next
cluster = previousClusterHint + 1;
if (allocationBitmap[cluster])
cluster = allocationBitmap.FindUnallocated();
}
allocationBitmap[cluster] = true;
return cluster;
return allocationBitmap.Allocate(previousClusterHint);
}
}

Expand All @@ -375,7 +363,7 @@ public void Free(DataDescriptor dataDescriptor)
var allocationBitmap = GetAllocationBitmap();
// TODO: optimize to write all only once
foreach (var cluster in GetClusters(dataDescriptor))
allocationBitmap[cluster] = false;
allocationBitmap.Free(cluster);
}
}

Expand All @@ -388,7 +376,7 @@ public void FreeCluster(Cluster cluster)
{
lock (_allocationLock)
{
GetAllocationBitmap()[cluster] = false;
GetAllocationBitmap().Free(cluster);
}
}

Expand Down Expand Up @@ -568,6 +556,7 @@ public ExFatUpCaseTable GetUpCaseTable()
return _upCaseTable;
}

private readonly object _allocationBitmapLock = new object();
private ExFatAllocationBitmap _allocationBitmap;

/// <summary>
Expand All @@ -576,15 +565,20 @@ public ExFatUpCaseTable GetUpCaseTable()
/// <returns></returns>
public ExFatAllocationBitmap GetAllocationBitmap()
{
if (_allocationBitmap == null)
lock (_allocationBitmapLock)
{
_allocationBitmap = new ExFatAllocationBitmap();
var allocationBitmapEntry = FindRootDirectoryEntries<AllocationBitmapExFatDirectoryEntry>()
.First(b => !b.BitmapFlags.Value.HasAny(AllocationBitmapFlags.SecondClusterBitmap));
var allocationBitmapStream = OpenDataStream(allocationBitmapEntry.DataDescriptor, FileAccess.ReadWrite);
_allocationBitmap.Open(allocationBitmapStream, allocationBitmapEntry.FirstCluster.Value, BootSector.ClusterCount.Value, _options.HasAny(ExFatOptions.DelayWrite));
if (_allocationBitmap == null)
{
_allocationBitmap = new ExFatAllocationBitmap();
var allocationBitmapEntry = FindRootDirectoryEntries<AllocationBitmapExFatDirectoryEntry>()
.First(b => !b.BitmapFlags.Value.HasAny(AllocationBitmapFlags.SecondClusterBitmap));
var allocationBitmapStream = OpenDataStream(allocationBitmapEntry.DataDescriptor, FileAccess.ReadWrite);
_allocationBitmap.Open(allocationBitmapStream, allocationBitmapEntry.FirstCluster.Value, BootSector.ClusterCount.Value,
_options.HasAny(ExFatOptions.DelayWrite));
}

return _allocationBitmap;
}
return _allocationBitmap;
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions ExFat.DiscUtils.Tests/Tests/PartitionStructureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public void CheckChecksums()
}
}

#if nomore
[TestMethod]
[TestCategory("Structure")]
public void AllocationBitmapExists()
Expand All @@ -99,5 +100,6 @@ public void AllocationBitmapExists()
Assert.IsFalse(bitmap[allocate10 + 9]);
}
}
#endif
}
}
6 changes: 6 additions & 0 deletions ExFat.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">160</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">This is ExFat, an exFAT accessor written in pure C#&#xD;
Released under MIT license&#xD;
https://github.com/picrap/ExFat</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FS/@EntryIndexedValue">FS</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

0 comments on commit c96b3fd

Please sign in to comment.