Skip to content

Commit

Permalink
Support for Fixed VHDX Files, and some package Updates (#12)
Browse files Browse the repository at this point in the history
* Added Experimental Support for .NET 9.0,
Implemented InitializeFixedInternal for vhdx files.
Added and turn on Tests for fixed VHDX files.

TODO: double check to make 100% sure that the test coverage covers the new added feature properly.

Tests as it stands all pass across all supported frameworks, additonal testing as required.

* Changes to test library project file to allow newer versions of packages across supported framework builds

* Removed .NET9.0 as target for build, this will be moved into it's own branch.

Changed FileParametersFlags.Fixed to FileParametersFlags.LeaveBlockAllocated and also put small comments on the invidiual flags.

All tests have been run, All pass across all frameworks(ran using 'dotnet test' command)

* fixed issue with file not allocating correctly, turns out fileEnd was still set at 1MB, this has been set to capacity.
Modified 2 tests as they try to allocate alomst 17GB of ram on a fixed disc, which then would fail.

All tests are passing, across all frameworks.
  • Loading branch information
TheEndHunter authored Jul 17, 2024
1 parent 427f65b commit 146943c
Show file tree
Hide file tree
Showing 15 changed files with 620 additions and 81 deletions.
8 changes: 0 additions & 8 deletions Library/DiscUtils.Core/DiscUtils.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
<Authors>Kenneth Bell;Quamotion;LordMike;Olof Lagerkvist</Authors>
<PackageTags>DiscUtils;VHD;VDI;XVA;VMDK;ISO;NTFS;EXT2FS</PackageTags>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\.editorconfig" Link=".editorconfig" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DiscUtils.Streams\DiscUtils.Streams.csproj" />
Expand All @@ -21,9 +17,5 @@
<ItemGroup Condition="'$(TargetFramework.CompareTo(`net5`))' &gt;= 0">
<PackageReference Include="System.Text.Encoding.CodePages" Version="*" />
</ItemGroup>

<ItemGroup>
<Folder Include="System\" />
</ItemGroup>

</Project>
40 changes: 30 additions & 10 deletions Library/DiscUtils.Vhdx/DiskBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,6 @@ public DiskStreamBuilder(SparseStream content, DiskType diskType, long blockSize

protected override List<BuilderExtent> FixExtents(out long totalLength)
{
if (_diskType != DiskType.Dynamic)
{
throw new NotSupportedException("Creation of Vhdx currently only supports dynamically expanding format");
}

var extents = new List<BuilderExtent>();

var logicalSectorSize = 512;
Expand Down Expand Up @@ -169,12 +164,37 @@ protected override List<BuilderExtent> FixExtents(out long totalLength)
extents.Add(ExtentForStruct(regionTable, 192 * Sizes.OneKiB));
extents.Add(ExtentForStruct(regionTable, 256 * Sizes.OneKiB));

// Metadata
var fileParams = new FileParameters
FileParameters fileParams;
if (_diskType == DiskType.Fixed)
{
BlockSize = (uint)_blockSize,
Flags = FileParametersFlags.None
};
// Metadata
fileParams = new FileParameters
{
BlockSize = (uint)_blockSize,
Flags = FileParametersFlags.LeaveBlocksAllocated
};
}
else if(_diskType == DiskType.Differencing)
{
fileParams = new FileParameters
{
BlockSize = (uint)_blockSize,
Flags = FileParametersFlags.HasParent
};
}
else if(_diskType == DiskType.Dynamic)
{
// Metadata
fileParams = new FileParameters
{
BlockSize = (uint)_blockSize,
Flags = FileParametersFlags.None
};
}
else
{
throw new InvalidOperationException("Unknown disk type");
}

var metadataBuffer = new byte[metadataRegion.Length];
var metadataStream = new MemoryStream(metadataBuffer);
Expand Down
104 changes: 96 additions & 8 deletions Library/DiscUtils.Vhdx/DiskImageFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,96 @@ protected override void Dispose(bool disposing)
}
}

public static byte[] oneMiB = new byte[1024 * 1024];
private static void InitializeFixedInternal(Stream stream, long capacity, Geometry? geometry)
{
throw new NotImplementedException();
geometry ??= Geometry.FromCapacity(capacity);

var logicalSectorSize = geometry.Value.BytesPerSector;
var physicalSectorSize = 4096;
const uint blockSize = FileParameters.DefaultFixedBlockSize;
var chunkRatio = 0x800000L * logicalSectorSize / blockSize;
var dataBlocksCount = MathUtilities.Ceil(capacity, blockSize);
var sectorBitmapBlocksCount = MathUtilities.Ceil(dataBlocksCount, chunkRatio);
var totalBatEntriesFixed = dataBlocksCount + sectorBitmapBlocksCount;
var fileHeader = new FileHeader { Creator = ".NET DiscUtils" };

long fileEnd = capacity;

var header1 = new VhdxHeader
{
SequenceNumber = 0,
FileWriteGuid = Guid.NewGuid(),
DataWriteGuid = Guid.NewGuid(),
LogGuid = Guid.Empty,
LogVersion = 0,
Version = 1,
LogLength = (uint)Sizes.OneMiB,
LogOffset = (ulong)fileEnd
};
header1.CalcChecksum();

fileEnd += header1.LogLength;

var header2 = new VhdxHeader(header1)
{
SequenceNumber = 1
};
header2.CalcChecksum();

var regionTable = new RegionTable();

var metadataRegion = new RegionEntry
{
Guid = RegionEntry.MetadataRegionGuid,
FileOffset = fileEnd,
Length = (uint)Sizes.OneMiB,
Flags = RegionFlags.Required
};
regionTable.Regions.Add(metadataRegion.Guid, metadataRegion);

fileEnd += metadataRegion.Length;

var batRegion = new RegionEntry
{
Guid = RegionEntry.BatGuid,
FileOffset = 3 * Sizes.OneMiB,
Length = (uint)MathUtilities.RoundUp(totalBatEntriesFixed * 8, Sizes.OneMiB),
Flags = RegionFlags.Required
};
regionTable.Regions.Add(batRegion.Guid, batRegion);

fileEnd += batRegion.Length;

stream.Position = 0;
stream.WriteStruct(fileHeader);

stream.Position = 64 * Sizes.OneKiB;
stream.WriteStruct(header1);

stream.Position = 128 * Sizes.OneKiB;
stream.WriteStruct(header2);

stream.Position = 192 * Sizes.OneKiB;
stream.WriteStruct(regionTable);

stream.Position = 256 * Sizes.OneKiB;
stream.WriteStruct(regionTable);

// Set stream to min size
stream.Position = fileEnd - 1;
stream.WriteByte(0);

// Metadata
var fileParams = new FileParameters
{
BlockSize = FileParameters.DefaultFixedBlockSize,
Flags = FileParametersFlags.LeaveBlocksAllocated,
};

var metadataStream = new SubStream(stream, metadataRegion.FileOffset, metadataRegion.Length);
_ = Metadata.Initialize(metadataStream, fileParams, (ulong)capacity,
(uint)logicalSectorSize, (uint)physicalSectorSize, null);
}

private static void InitializeDynamicInternal(Stream stream, long capacity, Geometry? geometry, long blockSize)
Expand Down Expand Up @@ -592,7 +679,7 @@ private static void InitializeDynamicInternal(Stream stream, long capacity, Geom
var fileParams = new FileParameters
{
BlockSize = (uint)blockSize,
Flags = FileParametersFlags.None
Flags = FileParametersFlags.None,
};

var metadataStream = new SubStream(stream, metadataRegion.FileOffset, metadataRegion.Length);
Expand All @@ -605,7 +692,8 @@ private static void InitializeDifferencingInternal(Stream stream, DiskImageFile
{
var logicalSectorSize = parent._metadata.LogicalSectorSize;
var physicalSectorSize = parent._metadata.PhysicalSectorSize;
var blockSize = parent._metadata.FileParameters.BlockSize;

uint blockSize = parent._metadata.FileParameters.BlockSize;
var capacity = parent._metadata.DiskSize;

var chunkRatio = 0x800000L * logicalSectorSize / blockSize;
Expand Down Expand Up @@ -685,7 +773,7 @@ private static void InitializeDifferencingInternal(Stream stream, DiskImageFile
var fileParams = new FileParameters
{
BlockSize = blockSize,
Flags = FileParametersFlags.HasParent
Flags = FileParametersFlags.HasParent,
};
var parentLocator = new ParentLocator(parent._header.DataWriteGuid.ToString("b"), parentRelativePath, parentAbsolutePath);

Expand Down Expand Up @@ -714,6 +802,7 @@ private void Initialize()
ReadMetadata();

_batStream = OpenRegion(RegionTable.BatGuid);

_freeSpace.Reserve(BatControlledFileExtents());

// Indicate the file is open for modification
Expand All @@ -728,10 +817,9 @@ private List<StreamExtent> BatControlledFileExtents()
{
_batStream.Position = 0;
var batData = _batStream.ReadExactly((int)_batStream.Length);

var blockSize = _metadata.FileParameters.BlockSize;
var chunkSize = (1L << 23) * _metadata.LogicalSectorSize;
var chunkRatio = (int)(chunkSize / _metadata.FileParameters.BlockSize);
uint blockSize = _metadata.FileParameters.BlockSize;
long chunkSize = (1L << 23) * _metadata.LogicalSectorSize;
int chunkRatio = (int)(chunkSize / blockSize);

var extents = new List<StreamExtent>();
for (var i = 0; i < batData.Length; i += 8)
Expand Down
10 changes: 8 additions & 2 deletions Library/DiscUtils.Vhdx/DiskImageFileInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,15 @@ public IEnumerable<LogEntryInfo> ActiveLogSequence
public bool HasParent => (_metadata.FileParameters.Flags & FileParametersFlags.HasParent) != 0;

/// <summary>
/// Gets a value indicating whether blocks should be left allocated within the file.
/// Gets a value indicaticating if (the VHDX file is a fixed disk.
/// </summary>
public bool LeaveBlocksAllocated => (_metadata.FileParameters.Flags & FileParametersFlags.LeaveBlocksAllocated) != 0;
public bool IsFixedDisk => (_metadata.FileParameters.Flags & FileParametersFlags.LeaveBlocksAllocated) != 0;

/// <summary>
/// Gets a value indicaticating if the VHDX file is a dynamic disk.
/// </summary>
public bool IsDynamicDisk => (_metadata.FileParameters.Flags & FileParametersFlags.LeaveBlocksAllocated) == 0;


/// <summary>
/// Gets the logical sector size of the disk represented by the VHDX file.
Expand Down
2 changes: 1 addition & 1 deletion Library/DiscUtils.Vhdx/DiskType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public enum DiskType
None = 0,

/// <summary>
/// Fixed-size disk, with space allocated up-front.
/// LeaveBlocksAllocated-size disk, with space allocated up-front.
/// </summary>
Fixed = 2,

Expand Down
1 change: 1 addition & 0 deletions Library/DiscUtils.Vhdx/FileParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace DiscUtils.Vhdx;

internal sealed class FileParameters : IByteArraySerializable
{
public const uint DefaultFixedBlockSize = (uint)Sizes.OneMiB;
public const uint DefaultBlockSize = 32 * (uint)Sizes.OneMiB;
public const uint DefaultDifferencingBlockSize = 2 * (uint)Sizes.OneMiB;
public const uint DefaultDynamicBlockSize = 32 * (uint)Sizes.OneMiB;
Expand Down
15 changes: 12 additions & 3 deletions Library/DiscUtils.Vhdx/FileParametersFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,16 @@ namespace DiscUtils.Vhdx;
[Flags]
internal enum FileParametersFlags : uint
{
None = 0x00,
LeaveBlocksAllocated = 0x01,
HasParent = 0x02
/// <summary>
/// If no bits are set, the disk will be considered Dynamic.
/// </summary>
None = 0,
/// <summary>
/// If set tells system to leave blocks allocated(fixed disk), otherwise it is a dynamic/differencing disk.
/// </summary>
LeaveBlocksAllocated = 1, //if bit 0 is set, the file is a fixed VHD, otherwise it is a dynamic/differencing VHDX.
/// <summary>
/// If this bit is set the file has a parent file, so it's most likely a differencing disk.
/// </summary>
HasParent = 2,
}
35 changes: 25 additions & 10 deletions Tests/LibraryTests/LibraryTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<SignAssembly>false</SignAssembly>
<LangVersion>Latest</LangVersion>
<AssemblyOriginatorKeyFile>../../SigningKey.snk</AssemblyOriginatorKeyFile>
<SkipDefaultAdapters>true</SkipDefaultAdapters>
<DisableSharedTestHost>true</DisableSharedTestHost>
</PropertyGroup>
<ItemGroup>
<None Remove="_Data\*" />
Expand All @@ -18,17 +20,30 @@
<ProjectReference Include="..\..\Library\DiscUtils.Core\DiscUtils.Core.csproj" />
<ProjectReference Include="..\..\Library\DiscUtils\DiscUtils.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework.CompareTo(`net462`))' &lt; 0 Or '$(TargetFramework)' == 'netcoreapp3.1'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.*" />
<PackageReference Include="System.IO.Compression" Version="4.3.*" />
<PackageReference Include="xunit.extensibility.core" Version="2.6.*" />
<PackageReference Include="xunit" Version="2.6.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

<ItemGroup Condition="'$(TargetFramework)' == 'net46' Or '$(TargetFramework)' == 'net461'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="System.IO.Compression" Version="*" />
<PackageReference Include="xunit" Version="2.9.*" />
<PackageReference Include="xunit.extensibility.core" Version="2.9.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework.CompareTo(`net462`))' &gt;= 0 And '$(TargetFramework.CompareTo(`net48`))' &lt;=0 Or '$(TargetFramework)' == 'netcoreapp3.1'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="System.IO.Compression" Version="*" />
<PackageReference Include="xunit" Version="2.9.*" />
<PackageReference Include="xunit.extensibility.core" Version="2.9.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.*">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework.CompareTo(`net462`))' &gt;= 0 And '$(TargetFramework)' != 'netcoreapp3.1'">

<ItemGroup Condition=" '$(TargetFramework.CompareTo(`net481`))' &gt;= 0 And '$(TargetFramework)' != 'netcoreapp3.1'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="*" />
<PackageReference Include="System.IO.Compression" Version="*" />
<PackageReference Include="xunit.extensibility.core" Version="*" />
Expand Down
39 changes: 28 additions & 11 deletions Tests/LibraryTests/Vhd/DiskBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,39 @@ namespace LibraryTests.Vhd;

public class DiskBuilderTest
{
private SparseStream diskContent;
private SparseStream dynamic_diskContent;
private SparseStream fixed_diskContent;

public DiskBuilderTest()
{
var fileStream = new MemoryStream();
var baseFile = Disk.InitializeDynamic(fileStream, Ownership.Dispose, 16 * 1024L * 1024);
for (var i = 0; i < 8; i += 1024 * 1024)
{
baseFile.Content.Position = i;
baseFile.Content.WriteByte((byte)i);
var fileStream = new MemoryStream();
var baseFile = Disk.InitializeDynamic(fileStream, Ownership.Dispose, 16 * 1024L * 1024);
for (var i = 0; i < 8; i += 1024 * 1024)
{
baseFile.Content.Position = i;
baseFile.Content.WriteByte((byte)i);
}

baseFile.Content.Position = 15 * 1024 * 1024;
baseFile.Content.WriteByte(0xFF);

dynamic_diskContent = baseFile.Content;
}
{
var fileStream = new MemoryStream();
var baseFile = Disk.InitializeFixed(fileStream, Ownership.Dispose, 16 * 1024L * 1024);
for (var i = 0; i < 8; i += 1024 * 1024)
{
baseFile.Content.Position = i;
baseFile.Content.WriteByte((byte)i);
}

baseFile.Content.Position = 15 * 1024 * 1024;
baseFile.Content.WriteByte(0xFF);
baseFile.Content.Position = 15 * 1024 * 1024;
baseFile.Content.WriteByte(0xFF);

diskContent = baseFile.Content;
fixed_diskContent = baseFile.Content;
}
}

[Fact]
Expand All @@ -54,7 +71,7 @@ public void BuildFixed()
var builder = new DiskBuilder
{
DiskType = FileType.Fixed,
Content = diskContent
Content = fixed_diskContent
};

var fileSpecs = builder.Build("foo").ToArray();
Expand All @@ -78,7 +95,7 @@ public void BuildDynamic()
var builder = new DiskBuilder
{
DiskType = FileType.Dynamic,
Content = diskContent
Content = dynamic_diskContent
};

var fileSpecs = builder.Build("foo").ToArray();
Expand Down
Loading

0 comments on commit 146943c

Please sign in to comment.