diff --git a/Library/DiscUtils.Core/DiscUtils.Core.csproj b/Library/DiscUtils.Core/DiscUtils.Core.csproj index 2f572d8d2..07ddb1a22 100644 --- a/Library/DiscUtils.Core/DiscUtils.Core.csproj +++ b/Library/DiscUtils.Core/DiscUtils.Core.csproj @@ -5,10 +5,6 @@ Kenneth Bell;Quamotion;LordMike;Olof Lagerkvist DiscUtils;VHD;VDI;XVA;VMDK;ISO;NTFS;EXT2FS - - - - @@ -21,9 +17,5 @@ - - - - diff --git a/Library/DiscUtils.Vhdx/DiskBuilder.cs b/Library/DiscUtils.Vhdx/DiskBuilder.cs index 062f157a5..6fb6472ad 100644 --- a/Library/DiscUtils.Vhdx/DiskBuilder.cs +++ b/Library/DiscUtils.Vhdx/DiskBuilder.cs @@ -100,11 +100,6 @@ public DiskStreamBuilder(SparseStream content, DiskType diskType, long blockSize protected override List FixExtents(out long totalLength) { - if (_diskType != DiskType.Dynamic) - { - throw new NotSupportedException("Creation of Vhdx currently only supports dynamically expanding format"); - } - var extents = new List(); var logicalSectorSize = 512; @@ -169,12 +164,37 @@ protected override List 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); diff --git a/Library/DiscUtils.Vhdx/DiskImageFile.cs b/Library/DiscUtils.Vhdx/DiskImageFile.cs index 6b11e723f..6c9491500 100644 --- a/Library/DiscUtils.Vhdx/DiskImageFile.cs +++ b/Library/DiscUtils.Vhdx/DiskImageFile.cs @@ -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) @@ -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); @@ -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; @@ -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); @@ -714,6 +802,7 @@ private void Initialize() ReadMetadata(); _batStream = OpenRegion(RegionTable.BatGuid); + _freeSpace.Reserve(BatControlledFileExtents()); // Indicate the file is open for modification @@ -728,10 +817,9 @@ private List 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(); for (var i = 0; i < batData.Length; i += 8) diff --git a/Library/DiscUtils.Vhdx/DiskImageFileInfo.cs b/Library/DiscUtils.Vhdx/DiskImageFileInfo.cs index 937251e5d..5a93d5d80 100644 --- a/Library/DiscUtils.Vhdx/DiskImageFileInfo.cs +++ b/Library/DiscUtils.Vhdx/DiskImageFileInfo.cs @@ -121,9 +121,15 @@ public IEnumerable ActiveLogSequence public bool HasParent => (_metadata.FileParameters.Flags & FileParametersFlags.HasParent) != 0; /// - /// 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. /// - public bool LeaveBlocksAllocated => (_metadata.FileParameters.Flags & FileParametersFlags.LeaveBlocksAllocated) != 0; + public bool IsFixedDisk => (_metadata.FileParameters.Flags & FileParametersFlags.LeaveBlocksAllocated) != 0; + + /// + /// Gets a value indicaticating if the VHDX file is a dynamic disk. + /// + public bool IsDynamicDisk => (_metadata.FileParameters.Flags & FileParametersFlags.LeaveBlocksAllocated) == 0; + /// /// Gets the logical sector size of the disk represented by the VHDX file. diff --git a/Library/DiscUtils.Vhdx/DiskType.cs b/Library/DiscUtils.Vhdx/DiskType.cs index aa14ef15a..5f870d193 100644 --- a/Library/DiscUtils.Vhdx/DiskType.cs +++ b/Library/DiscUtils.Vhdx/DiskType.cs @@ -33,7 +33,7 @@ public enum DiskType None = 0, /// - /// Fixed-size disk, with space allocated up-front. + /// LeaveBlocksAllocated-size disk, with space allocated up-front. /// Fixed = 2, diff --git a/Library/DiscUtils.Vhdx/FileParameters.cs b/Library/DiscUtils.Vhdx/FileParameters.cs index 76a88ccc7..6a694eae8 100644 --- a/Library/DiscUtils.Vhdx/FileParameters.cs +++ b/Library/DiscUtils.Vhdx/FileParameters.cs @@ -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; diff --git a/Library/DiscUtils.Vhdx/FileParametersFlags.cs b/Library/DiscUtils.Vhdx/FileParametersFlags.cs index 62f71145e..ec50fdb2a 100644 --- a/Library/DiscUtils.Vhdx/FileParametersFlags.cs +++ b/Library/DiscUtils.Vhdx/FileParametersFlags.cs @@ -27,7 +27,16 @@ namespace DiscUtils.Vhdx; [Flags] internal enum FileParametersFlags : uint { - None = 0x00, - LeaveBlocksAllocated = 0x01, - HasParent = 0x02 + /// + /// If no bits are set, the disk will be considered Dynamic. + /// + None = 0, + /// + /// If set tells system to leave blocks allocated(fixed disk), otherwise it is a dynamic/differencing disk. + /// + LeaveBlocksAllocated = 1, //if bit 0 is set, the file is a fixed VHD, otherwise it is a dynamic/differencing VHDX. + /// + /// If this bit is set the file has a parent file, so it's most likely a differencing disk. + /// + HasParent = 2, } \ No newline at end of file diff --git a/Tests/LibraryTests/LibraryTests.csproj b/Tests/LibraryTests/LibraryTests.csproj index b754a2783..101ac1d3e 100644 --- a/Tests/LibraryTests/LibraryTests.csproj +++ b/Tests/LibraryTests/LibraryTests.csproj @@ -8,6 +8,8 @@ false Latest ../../SigningKey.snk + true + true @@ -18,17 +20,30 @@ - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - + + diff --git a/Tests/LibraryTests/Vhd/DiskBuilderTest.cs b/Tests/LibraryTests/Vhd/DiskBuilderTest.cs index bbc090673..4606149ba 100644 --- a/Tests/LibraryTests/Vhd/DiskBuilderTest.cs +++ b/Tests/LibraryTests/Vhd/DiskBuilderTest.cs @@ -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] @@ -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(); @@ -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(); diff --git a/Tests/LibraryTests/Vhd/DiskImageFileTest.cs b/Tests/LibraryTests/Vhd/DiskImageFileTest.cs index 2a66e556c..d58c3080b 100644 --- a/Tests/LibraryTests/Vhd/DiskImageFileTest.cs +++ b/Tests/LibraryTests/Vhd/DiskImageFileTest.cs @@ -66,7 +66,7 @@ public void GetParentLocations() using (var diffFile = new DiskImageFile(diffStream)) { var BasePath = @"E:\FOO\"; -#if NET461_OR_GREATER || NETSTANDARD || NETCOREAPP +#if NET471_OR_GREATER || NETSTANDARD || NETCOREAPP if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { BasePath = "/foo/"; diff --git a/Tests/LibraryTests/Vhdx/DiskBuilderTest.cs b/Tests/LibraryTests/Vhdx/DiskBuilderTest.cs index e1bfd6128..314656cd1 100644 --- a/Tests/LibraryTests/Vhdx/DiskBuilderTest.cs +++ b/Tests/LibraryTests/Vhdx/DiskBuilderTest.cs @@ -32,31 +32,49 @@ namespace LibraryTests.Vhdx; public class DiskBuilderTest { - private SparseStream diskContent; + private SparseStream dynamicDiskContent; + private SparseStream fixedDiskContent; public DiskBuilderTest() { - var sourceStream = new SparseMemoryStream(); - sourceStream.SetLength(160 * 1024L * 1024); - for (var i = 0; i < 8; ++i) { - sourceStream.Position = i * 1024L * 1024; - sourceStream.WriteByte((byte)i); + var sourceStream = new SparseMemoryStream(); + sourceStream.SetLength(160 * 1024L * 1024); + for (var i = 0; i < 8; ++i) + { + sourceStream.Position = i * 1024L * 1024; + sourceStream.WriteByte((byte)i); + } + + sourceStream.Position = 150 * 1024 * 1024; + sourceStream.WriteByte(0xFF); + + dynamicDiskContent = sourceStream; } - sourceStream.Position = 150 * 1024 * 1024; - sourceStream.WriteByte(0xFF); + { + var sourceStream = new SparseMemoryStream(); + sourceStream.SetLength(160 * 1024L * 1024); + for (var i = 0; i < 8; ++i) + { + sourceStream.Position = i * 1024L * 1024; + sourceStream.WriteByte((byte)i); + } + + sourceStream.Position = 150 * 1024 * 1024; + sourceStream.WriteByte(0xFF); - diskContent = sourceStream; + fixedDiskContent = sourceStream; + } } - [Fact(Skip = "Fixed size Vhdx not implemeted")] + [Fact()] public void BuildFixed() { var builder = new DiskBuilder { DiskType = DiskType.Fixed, - Content = diskContent + Content = fixedDiskContent }; var fileSpecs = builder.Build("foo").ToArray(); @@ -74,6 +92,23 @@ public void BuildFixed() Assert.Equal(0xFF, disk.Content.ReadByte()); } + [Fact()] + public void BuildEmptyFixed() + { + var builder = new DiskBuilder + { + DiskType = DiskType.Fixed, + Content = new SparseMemoryStream() + }; + + var fileSpecs = builder.Build("foo").ToArray(); + Assert.Single(fileSpecs); + Assert.Equal("foo.vhdx", fileSpecs[0].Name); + + using var disk = new Disk(fileSpecs[0].OpenStream(), Ownership.Dispose); + Assert.Equal(0, disk.Content.Length); + } + [Fact] public void BuildEmptyDynamic() { @@ -170,7 +205,7 @@ public void BuildDynamic() var builder = new DiskBuilder { DiskType = DiskType.Dynamic, - Content = diskContent + Content = dynamicDiskContent }; var fileSpecs = builder.Build("foo").ToArray(); diff --git a/Tests/LibraryTests/Vhdx/DiskTest.cs b/Tests/LibraryTests/Vhdx/DiskTest.cs index 5b7285c5d..abe941c12 100644 --- a/Tests/LibraryTests/Vhdx/DiskTest.cs +++ b/Tests/LibraryTests/Vhdx/DiskTest.cs @@ -36,10 +36,10 @@ public class DiskTest public void InitializeFixed() { var ms = new MemoryStream(); - using (var disk = Disk.InitializeDynamic(ms, Ownership.None, 8 * 1024 * 1024)) + using (var disk = Disk.InitializeDynamic(ms, Ownership.None, 8 * Sizes.OneMiB)) { Assert.NotNull(disk); - Assert.True(disk.Geometry.Value.Capacity is > (long)(7.5 * 1024 * 1024) and <= (8 * 1024 * 1024)); + Assert.True(disk.Geometry.Value.Capacity is > (long)(7.5 * Sizes.OneMiB) and <= (8 * Sizes.OneMiB)); } // Check the stream is still valid @@ -47,11 +47,23 @@ public void InitializeFixed() ms.Dispose(); } + [Fact] + public void InitializeFixedOwnStream() + { + var ms = new MemoryStream(); + using (var disk = Disk.InitializeFixed(ms, Ownership.Dispose, 8 * Sizes.OneMiB)) + { + } + + Assert.Throws(() => ms.ReadByte()); + } + + [Fact] public void InitializeDynamicOwnStream() { var ms = new MemoryStream(); - using (var disk = Disk.InitializeDynamic(ms, Ownership.Dispose, 8 * 1024 * 1024)) + using (var disk = Disk.InitializeDynamic(ms, Ownership.Dispose, 8 * Sizes.OneMiB)) { } @@ -62,17 +74,17 @@ public void InitializeDynamicOwnStream() public void InitializeDynamic() { var ms = new MemoryStream(); - using (var disk = Disk.InitializeDynamic(ms, Ownership.None, 16 * 1024L * 1024 * 1024)) + using (var disk = Disk.InitializeDynamic(ms, Ownership.None, 16 * Sizes.OneGiB)) { Assert.NotNull(disk); - Assert.True(disk.Geometry.Value.Capacity is > (long)(15.8 * 1024L * 1024 * 1024) and <= (16 * 1024L * 1024 * 1024)); + Assert.True(disk.Geometry.Value.Capacity is > (long)(15.8 * Sizes.OneGiB) and <= (16 * Sizes.OneGiB)); } - Assert.True(8 * 1024 * 1024 > ms.Length); + Assert.True(8 * Sizes.OneMiB > ms.Length); using (var disk = new Disk(ms, Ownership.Dispose)) { - Assert.True(disk.Geometry.Value.Capacity is > (long)(15.8 * 1024L * 1024 * 1024) and <= (16 * 1024L * 1024 * 1024)); + Assert.True(disk.Geometry.Value.Capacity is > (long)(15.8 * Sizes.OneGiB) and <= (16 * Sizes.OneGiB)); } } @@ -81,16 +93,16 @@ public void InitializeDifferencing() { var baseStream = new MemoryStream(); var diffStream = new MemoryStream(); - var baseFile = DiskImageFile.InitializeDynamic(baseStream, Ownership.Dispose, 16 * 1024L * 1024 * 1024); + var baseFile = DiskImageFile.InitializeDynamic(baseStream, Ownership.Dispose, 16 * Sizes.OneGiB); using (var disk = Disk.InitializeDifferencing(diffStream, Ownership.None, baseFile, Ownership.Dispose, @"C:\TEMP\Base.vhd", @".\Base.vhd", DateTime.UtcNow)) { Assert.NotNull(disk); - Assert.True(disk.Geometry.Value.Capacity is > (long)(15.8 * 1024L * 1024 * 1024) and <= (16 * 1024L * 1024 * 1024)); + Assert.True(disk.Geometry.Value.Capacity is > (long)(15.8 * Sizes.OneGiB) and <= (16 * Sizes.OneGiB)); Assert.True(disk.Geometry.Value.Capacity == baseFile.Geometry.Capacity); Assert.Equal(2, new List(disk.Layers).Count); } - Assert.True(8 * 1024 * 1024 > diffStream.Length); + Assert.True(8 * Sizes.OneMiB > diffStream.Length); diffStream.Dispose(); } @@ -99,7 +111,30 @@ public void ConstructorDynamic() { Geometry geometry; var ms = new MemoryStream(); - using (var disk = Disk.InitializeDynamic(ms, Ownership.None, 16 * 1024L * 1024 * 1024)) + using (var disk = Disk.InitializeDynamic(ms, Ownership.None, 16 * Sizes.OneGiB)) + { + geometry = disk.Geometry.Value; + } + + using (var disk = new Disk(ms, Ownership.None)) + { + Assert.Equal(geometry, disk.Geometry); + Assert.NotNull(disk.Content); + } + + using (var disk = new Disk(ms, Ownership.Dispose)) + { + Assert.Equal(geometry, disk.Geometry); + Assert.NotNull(disk.Content); + } + } + + [Fact] + public void ConstructorFixed() + { + Geometry geometry; + var ms = new MemoryStream(); + using (var disk = Disk.InitializeFixed(ms, Ownership.None, 16 * Sizes.OneMiB)) { geometry = disk.Geometry.Value; } @@ -118,10 +153,10 @@ public void ConstructorDynamic() } [Fact] - public void ConstructorFromFiles() + public void ConstructorFromFilesDynamic() { var baseStream = new MemoryStream(); - var baseFile = DiskImageFile.InitializeDynamic(baseStream, Ownership.Dispose, 16 * 1024L * 1024 * 1024); + var baseFile = DiskImageFile.InitializeDynamic(baseStream, Ownership.Dispose, 16 * Sizes.OneGiB); var childStream = new MemoryStream(); var childFile = DiskImageFile.InitializeDifferencing(childStream, Ownership.Dispose, baseFile, @"C:\temp\foo.vhd", @".\foo.vhd", DateTime.Now); @@ -133,4 +168,21 @@ public void ConstructorFromFiles() Assert.NotNull(disk.Content); } + [Fact] + public void ConstructorFromFilesFixed() + { + var baseStream = new MemoryStream(); + var baseFile = DiskImageFile.InitializeFixed(baseStream, Ownership.Dispose, 16 * Sizes.OneMiB); + + var childStream = new MemoryStream(); + var childFile = DiskImageFile.InitializeDifferencing(childStream, Ownership.Dispose, baseFile, @"C:\temp\foo.vhd", @".\foo.vhd", DateTime.Now); + + var grandChildStream = new MemoryStream(); + var grandChildFile = DiskImageFile.InitializeDifferencing(grandChildStream, Ownership.Dispose, childFile, @"C:\temp\child1.vhd", @".\child1.vhd", DateTime.Now); + + using var disk = new Disk(new DiskImageFile[] { grandChildFile, childFile, baseFile }, Ownership.Dispose); + Assert.NotNull(disk.Content); + } + + } diff --git a/Utilities/VHDXCreate/Program.cs b/Utilities/VHDXCreate/Program.cs index c5aa8c441..3e37a28dc 100644 --- a/Utilities/VHDXCreate/Program.cs +++ b/Utilities/VHDXCreate/Program.cs @@ -116,7 +116,7 @@ protected override void DoRun() return; } - // Create Fixed disk + // Create LeaveBlocksAllocated disk using var fs = new FileStream(_destFile.Value, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete, bufferSize: 2 << 20); Disk.InitializeFixed(fs, Ownership.None, DiskSize); } diff --git a/Utilities/VHDXDump/Program.cs b/Utilities/VHDXDump/Program.cs index 1b00bd320..402a94273 100644 --- a/Utilities/VHDXDump/Program.cs +++ b/Utilities/VHDXDump/Program.cs @@ -66,7 +66,7 @@ protected override void DoRun() Console.WriteLine(" Signature: {0:x8}", info.Signature); Console.WriteLine(" Creator: {0:x8}", info.Creator); Console.WriteLine(" Block Size: {0} (0x{0:X8})", info.BlockSize); - Console.WriteLine("Leave Blocks Alloced: {0}", info.LeaveBlocksAllocated); + Console.WriteLine(" LeaveBlocksAllocated: {0}", info.IsFixedDisk); Console.WriteLine(" Has Parent: {0}", info.HasParent); Console.WriteLine(" Disk Size: {0} ({1} (0x{1:X8}))", Utilities.ApproximateDiskSize(info.DiskSize), info.DiskSize); Console.WriteLine(" Logical Sector Size: {0} (0x{0:X8})", info.LogicalSectorSize); diff --git a/workload-install.ps1 b/workload-install.ps1 new file mode 100644 index 000000000..fa0c0311c --- /dev/null +++ b/workload-install.ps1 @@ -0,0 +1,304 @@ +# +# Copyright (c) Samsung Electronics. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +<# +.SYNOPSIS +Installs Tizen workload manifest. +.DESCRIPTION +Installs the WorkloadManifest.json and WorkloadManifest.targets files for Tizen to the dotnet sdk. +.PARAMETER Version +Use specific VERSION +.PARAMETER DotnetInstallDir +Dotnet SDK Location installed +#> + +[cmdletbinding()] +param( + [Alias('v')][string]$Version="", + [Alias('d')][string]$DotnetInstallDir="", + [Alias('t')][string]$DotnetTargetVersionBand="", + [Alias('u')][switch]$UpdateAllWorkloads +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" +$ProgressPreference = "SilentlyContinue" + +$ManifestBaseName = "Samsung.NET.Sdk.Tizen.Manifest" + +$LatestVersionMap = @{ + "$ManifestBaseName-6.0.100" = "7.0.101"; + "$ManifestBaseName-6.0.200" = "7.0.100-preview.13.6"; + "$ManifestBaseName-6.0.300" = "8.0.133"; + "$ManifestBaseName-6.0.400" = "8.0.140"; + "$ManifestBaseName-7.0.100-preview.6" = "7.0.100-preview.6.14"; + "$ManifestBaseName-7.0.100-preview.7" = "7.0.100-preview.7.20"; + "$ManifestBaseName-7.0.100-rc.1" = "7.0.100-rc.1.22"; + "$ManifestBaseName-7.0.100-rc.2" = "7.0.100-rc.2.24"; + "$ManifestBaseName-7.0.100" = "7.0.103"; + "$ManifestBaseName-7.0.200" = "7.0.105"; + "$ManifestBaseName-7.0.300" = "7.0.120"; + "$ManifestBaseName-7.0.400" = "8.0.141"; + "$ManifestBaseName-8.0.100-alpha.1" = "7.0.104"; + "$ManifestBaseName-8.0.100-preview.2" = "7.0.106"; + "$ManifestBaseName-8.0.100-preview.3" = "7.0.107"; + "$ManifestBaseName-8.0.100-preview.4" = "7.0.108"; + "$ManifestBaseName-8.0.100-preview.5" = "7.0.110"; + "$ManifestBaseName-8.0.100-preview.6" = "7.0.121"; + "$ManifestBaseName-8.0.100-preview.7" = "7.0.122"; + "$ManifestBaseName-8.0.100-rc.1" = "7.0.124"; + "$ManifestBaseName-8.0.100-rc.2" = "7.0.125"; + "$ManifestBaseName-8.0.100-rtm" = "7.0.127"; + "$ManifestBaseName-8.0.100" = "8.0.144"; + "$ManifestBaseName-8.0.200" = "8.0.145"; + "$ManifestBaseName-8.0.300" = "8.0.149"; + "$ManifestBaseName-9.0.100-alpha.1" = "8.0.134"; + "$ManifestBaseName-9.0.100-preview.1" = "8.0.135"; + "$ManifestBaseName-9.0.100-preview.2" = "8.0.137"; +} + +function New-TemporaryDirectory { + $parent = [System.IO.Path]::GetTempPath() + $name = [System.IO.Path]::GetRandomFileName() + New-Item -ItemType Directory -Path (Join-Path $parent $name) +} + +function Ensure-Directory([string]$TestDir) { + Try { + New-Item -ItemType Directory -Path $TestDir -Force -ErrorAction stop | Out-Null + [io.file]::OpenWrite($(Join-Path -Path $TestDir -ChildPath ".test-write-access")).Close() + Remove-Item -Path $(Join-Path -Path $TestDir -ChildPath ".test-write-access") -Force + } + Catch [System.UnauthorizedAccessException] { + Write-Error "No permission to install. Try run with administrator mode." + } +} + +function Get-LatestVersion([string]$Id) { + $attempts=3 + $sleepInSeconds=3 + do + { + try + { + $Response = Invoke-WebRequest -Uri https://api.nuget.org/v3-flatcontainer/$Id/index.json -UseBasicParsing | ConvertFrom-Json + return $Response.versions | Select-Object -Last 1 + } + catch { + Write-Host "Id: $Id" + Write-Host "An exception was caught: $($_.Exception.Message)" + } + + $attempts-- + if ($attempts -gt 0) { Start-Sleep $sleepInSeconds } + } while ($attempts -gt 0) + + if ($LatestVersionMap.ContainsKey($Id)) + { + Write-Host "Return cached latest version." + return $LatestVersionMap.$Id + } else { + Write-Error "Wrong Id: $Id" + } +} + +function Get-Package([string]$Id, [string]$Version, [string]$Destination, [string]$FileExt = "nupkg") { + $OutFileName = "$Id.$Version.$FileExt" + $OutFilePath = Join-Path -Path $Destination -ChildPath $OutFileName + + if ($Id -match ".net[0-9]+$") { + $Id = $Id -replace (".net[0-9]+", "") + } + + Invoke-WebRequest -Uri "https://www.nuget.org/api/v2/package/$Id/$Version" -OutFile $OutFilePath + + return $OutFilePath +} + +function Install-Pack([string]$Id, [string]$Version, [string]$Kind) { + $TempZipFile = $(Get-Package -Id $Id -Version $Version -Destination $TempDir -FileExt "zip") + $TempUnzipDir = Join-Path -Path $TempDir -ChildPath "unzipped\$Id" + + switch ($Kind) { + "manifest" { + Expand-Archive -Path $TempZipFile -DestinationPath $TempUnzipDir + New-Item -Path $TizenManifestDir -ItemType "directory" -Force | Out-Null + Copy-Item -Path "$TempUnzipDir\data\*" -Destination $TizenManifestDir -Force + } + {($_ -eq "sdk") -or ($_ -eq "framework")} { + Expand-Archive -Path $TempZipFile -DestinationPath $TempUnzipDir + if ( ($kind -eq "sdk") -and ($Id -match ".net[0-9]+$")) { + $Id = $Id -replace (".net[0-9]+", "") + } + $TargetDirectory = $(Join-Path -Path $DotnetInstallDir -ChildPath "packs\$Id\$Version") + New-Item -Path $TargetDirectory -ItemType "directory" -Force | Out-Null + Copy-Item -Path "$TempUnzipDir/*" -Destination $TargetDirectory -Recurse -Force + } + "template" { + $TargetFileName = "$Id.$Version.nupkg".ToLower() + $TargetDirectory = $(Join-Path -Path $DotnetInstallDir -ChildPath "template-packs") + New-Item -Path $TargetDirectory -ItemType "directory" -Force | Out-Null + Copy-Item $TempZipFile -Destination $(Join-Path -Path $TargetDirectory -ChildPath "$TargetFileName") -Force + } + } +} + +function Remove-Pack([string]$Id, [string]$Version, [string]$Kind) { + switch ($Kind) { + "manifest" { + Remove-Item -Path $TizenManifestDir -Recurse -Force + } + {($_ -eq "sdk") -or ($_ -eq "framework")} { + $TargetDirectory = $(Join-Path -Path $DotnetInstallDir -ChildPath "packs\$Id\$Version") + Remove-Item -Path $TargetDirectory -Recurse -Force + } + "template" { + $TargetFileName = "$Id.$Version.nupkg".ToLower(); + Remove-Item -Path $(Join-Path -Path $DotnetInstallDir -ChildPath "template-packs\$TargetFileName") -Force + } + } +} + +function Install-TizenWorkload([string]$DotnetVersion) +{ + $VersionSplitSymbol = '.' + $SplitVersion = $DotnetVersion.Split($VersionSplitSymbol) + + $CurrentDotnetVersion = [Version]"$($SplitVersion[0]).$($SplitVersion[1])" + $DotnetVersionBand = $SplitVersion[0] + $VersionSplitSymbol + $SplitVersion[1] + $VersionSplitSymbol + $SplitVersion[2][0] + "00" + $ManifestName = "$ManifestBaseName-$DotnetVersionBand" + + if ($DotnetTargetVersionBand -eq "" -or $UpdateAllWorkloads.IsPresent) { + if ($CurrentDotnetVersion -ge "7.0") + { + $IsPreviewVersion = $DotnetVersion.Contains("-preview") -or $DotnetVersion.Contains("-rc") -or $DotnetVersion.Contains("-alpha") + if ($IsPreviewVersion -and ($SplitVersion.Count -ge 4)) { + $DotnetTargetVersionBand = $DotnetVersionBand + $SplitVersion[2].SubString(3) + $VersionSplitSymbol + $($SplitVersion[3]) + $ManifestName = "$ManifestBaseName-$DotnetTargetVersionBand" + } + elseif ($DotnetVersion.Contains("-rtm") -and ($SplitVersion.Count -ge 3)) { + $DotnetTargetVersionBand = $DotnetVersionBand + $SplitVersion[2].SubString(3) + $ManifestName = "$ManifestBaseName-$DotnetTargetVersionBand" + } + else { + $DotnetTargetVersionBand = $DotnetVersionBand + } + } + else { + $DotnetTargetVersionBand = $DotnetVersionBand + } + } + + # Check latest version of manifest. + if ($Version -eq "" -or $UpdateAllWorkloads.IsPresent) { + $Version = Get-LatestVersion -Id $ManifestName + } + + # Check workload manifest directory. + $ManifestDir = Join-Path -Path $DotnetInstallDir -ChildPath "sdk-manifests" | Join-Path -ChildPath $DotnetTargetVersionBand + $TizenManifestDir = Join-Path -Path $ManifestDir -ChildPath "samsung.net.sdk.tizen" + $TizenManifestFile = Join-Path -Path $TizenManifestDir -ChildPath "WorkloadManifest.json" + + # Check and remove already installed old version. + if (Test-Path $TizenManifestFile) { + $ManifestJson = $(Get-Content $TizenManifestFile | ConvertFrom-Json) + $OldVersion = $ManifestJson.version + if ($OldVersion -eq $Version) { + $DotnetWorkloadList = Invoke-Expression "& '$DotnetCommand' workload list | Select-String -Pattern '^tizen'" + if ($DotnetWorkloadList) + { + Write-Host "Tizen Workload $Version version is already installed." + Continue + } + } + + Ensure-Directory $ManifestDir + Write-Host "Removing $ManifestName/$OldVersion from $ManifestDir..." + Remove-Pack -Id $ManifestName -Version $OldVersion -Kind "manifest" + $ManifestJson.packs.PSObject.Properties | ForEach-Object { + Write-Host "Removing $($_.Name)/$($_.Value.version)..." + Remove-Pack -Id $_.Name -Version $_.Value.version -Kind $_.Value.kind + } + } + + Ensure-Directory $ManifestDir + $TempDir = $(New-TemporaryDirectory) + + # Install workload manifest. + Write-Host "Installing $ManifestName/$Version to $ManifestDir..." + Install-Pack -Id $ManifestName -Version $Version -Kind "manifest" + + # Download and install workload packs. + $NewManifestJson = $(Get-Content $TizenManifestFile | ConvertFrom-Json) + $NewManifestJson.packs.PSObject.Properties | ForEach-Object { + Write-Host "Installing $($_.Name)/$($_.Value.version)..." + Install-Pack -Id $_.Name -Version $_.Value.version -Kind $_.Value.kind + } + + # Add tizen to the installed workload metadata. + # Featured version band for metadata does NOT include any preview specifier. + # https://github.com/dotnet/sdk/blob/main/documentation/general/workloads/user-local-workloads.md + New-Item -Path $(Join-Path -Path $DotnetInstallDir -ChildPath "metadata\workloads\$DotnetVersionBand\InstalledWorkloads\tizen") -Force | Out-Null + if (Test-Path $(Join-Path -Path $DotnetInstallDir -ChildPath "metadata\workloads\$DotnetVersionBand\InstallerType\msi")) { + New-Item -Path "HKLM:\SOFTWARE\Microsoft\dotnet\InstalledWorkloads\Standalone\x64\$DotnetTargetVersionBand\tizen" -Force | Out-Null + } + + # Clean up + Remove-Item -Path $TempDir -Force -Recurse + + Write-Host "Done installing Tizen workload $Version" +} + +# Check dotnet install directory. +if ($DotnetInstallDir -eq "") { + if ($Env:DOTNET_ROOT -And $(Test-Path "$Env:DOTNET_ROOT")) { + $DotnetInstallDir = $Env:DOTNET_ROOT + } else { + $DotnetInstallDir = Join-Path -Path $Env:Programfiles -ChildPath "dotnet" + } +} +if (-Not $(Test-Path "$DotnetInstallDir")) { + Write-Error "No installed dotnet '$DotnetInstallDir'." +} + +# Check installed dotnet version +$DotnetCommand = "$DotnetInstallDir\dotnet" +if (Get-Command $DotnetCommand -ErrorAction SilentlyContinue) +{ + if ($UpdateAllWorkloads.IsPresent) + { + $InstalledDotnetSdks = Invoke-Expression "& '$DotnetCommand' --list-sdks | Select-String -Pattern '^6|^7'" | ForEach-Object {$_ -replace (" \[.*","")} + } + else + { + $InstalledDotnetSdks = Invoke-Expression "& '$DotnetCommand' --version" + } +} +else +{ + Write-Error "'$DotnetCommand' occurs an error." +} + +if (-Not $InstalledDotnetSdks) +{ + Write-Host "`n.NET SDK version 6 or later is required to install Tizen Workload." +} +else +{ + foreach ($DotnetSdk in $InstalledDotnetSdks) + { + try { + Write-Host "`nCheck Tizen Workload for sdk $DotnetSdk" + Install-TizenWorkload -DotnetVersion $DotnetSdk + } + catch { + Write-Host "Failed to install Tizen Workload for sdk $DotnetSdk" + Write-Host "$_" + Continue + } + } +} + +Write-Host "`nDone"