Browse Source

Updated DiskAccessLibrary to v1.4.2

Tal Aloni 7 years ago
parent
commit
baf6417722
46 changed files with 906 additions and 678 deletions
  1. 14 14
      DiskAccessLibrary/DiskAccessLibrary.csproj
  2. 2 13
      DiskAccessLibrary/Disks/DiskImage.cs
  3. 119 19
      DiskAccessLibrary/Disks/RawDiskImage/RawDiskImage.cs
  4. 5 5
      DiskAccessLibrary/Disks/VHD/BlockAllocationTable.cs
  5. 25 50
      DiskAccessLibrary/Disks/VHD/VirtualHardDisk.cs
  6. 11 3
      DiskAccessLibrary/Disks/VMDK/SparseExtent.cs
  7. 9 6
      DiskAccessLibrary/Disks/VMDK/VirtualMachineDisk.cs
  8. 1 1
      DiskAccessLibrary/FileSystems/IExtendableFileSystem.cs
  9. 2 2
      DiskAccessLibrary/FileSystems/NTFS/Adapters/NTFSFileSystem.cs
  10. 13 13
      DiskAccessLibrary/FileSystems/NTFS/NTFSVolume.Extend.cs
  11. 42 1
      DiskAccessLibrary/Helpers/BasicDiskHelper.cs
  12. 59 0
      DiskAccessLibrary/Helpers/DiskExtentsHelper.cs
  13. 16 16
      DiskAccessLibrary/Helpers/ExtendHelper.Volume.cs
  14. 0 0
      DiskAccessLibrary/LogicalDiskManager/DynamicDiskExtent.cs
  15. 10 214
      DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicDiskExtentHelper.cs
  16. 30 0
      DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicDiskExtentsHelper.cs
  17. 185 0
      DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicDiskHelper.Extents.cs
  18. 1 72
      DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicDiskHelper.cs
  19. 6 4
      DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicVolumeHelper.cs
  20. 103 0
      DiskAccessLibrary/LogicalDiskManager/Helpers/PrivateRegionHelper.cs
  21. 51 0
      DiskAccessLibrary/LogicalDiskManager/Helpers/PublicRegionHelper.cs
  22. 23 23
      DiskAccessLibrary/LogicalDiskManager/Helpers/VolumeManagerDatabaseHelper.cs
  23. 27 8
      DiskAccessLibrary/LogicalDiskManager/KernelUpdateLog/KernelUpdateLogPage.cs
  24. 14 36
      DiskAccessLibrary/LogicalDiskManager/PrivateHeader.cs
  25. 20 6
      DiskAccessLibrary/LogicalDiskManager/TOCBlock/TOCBlock.cs
  26. 10 7
      DiskAccessLibrary/LogicalDiskManager/VolumeManagerDatabase.cs
  27. 14 2
      DiskAccessLibrary/LogicalDiskManager/VolumeManagerDatabaseHeader.cs
  28. 41 4
      DiskAccessLibrary/LogicalDiskManager/Volumes/DynamicColumn.cs
  29. 32 7
      DiskAccessLibrary/LogicalDiskManager/Volumes/DynamicVolume.cs
  30. 2 10
      DiskAccessLibrary/LogicalDiskManager/Volumes/Raid5Volume.cs
  31. 0 0
      DiskAccessLibrary/PartitionTables/GuidPartitionTable/GuidPartitionEntry.cs
  32. 0 0
      DiskAccessLibrary/PartitionTables/GuidPartitionTable/GuidPartitionEntryCollection.cs
  33. 0 0
      DiskAccessLibrary/PartitionTables/GuidPartitionTable/GuidPartitionTable.cs
  34. 0 0
      DiskAccessLibrary/PartitionTables/GuidPartitionTable/GuidPartitionTableHeader.cs
  35. 0 0
      DiskAccessLibrary/PartitionTables/MasterBootRecord/CHSAddress.cs
  36. 0 0
      DiskAccessLibrary/PartitionTables/MasterBootRecord/MasterBootRecord.cs
  37. 0 0
      DiskAccessLibrary/PartitionTables/MasterBootRecord/PartitionTableEntry.cs
  38. 0 0
      DiskAccessLibrary/PartitionTables/MasterBootRecord/PartitionTypeName.cs
  39. 2 2
      DiskAccessLibrary/Properties/AssemblyInfo.cs
  40. 8 0
      DiskAccessLibrary/RevisionHistory.txt
  41. 9 1
      DiskAccessLibrary/Volumes/Partition.cs
  42. 0 18
      DiskAccessLibrary/Win32/Disks/DiskImage.Win32.cs
  43. 0 42
      DiskAccessLibrary/Win32/Disks/RawDiskImage/RawDiskImage.Win32.cs
  44. 0 39
      DiskAccessLibrary/Win32/Disks/VHD/VirtualHardDisk.Win32.cs
  45. 0 20
      DiskAccessLibrary/Win32/Disks/VMDK/SparseExtent.Win32.cs
  46. 0 20
      DiskAccessLibrary/Win32/Disks/VMDK/VirtualMachineDisk.Win32.cs

+ 14 - 14
DiskAccessLibrary/DiskAccessLibrary.csproj

@@ -35,16 +35,7 @@
     <Compile Include="BaseClasses\DiskExtent.cs" />
     <Compile Include="BaseClasses\IDiskGeometry.cs" />
     <Compile Include="BaseClasses\Volume.cs" />
-    <Compile Include="DiskExtent\DynamicDiskExtent.cs" />
     <Compile Include="Disks\DiskImage.cs" />
-    <Compile Include="Disks\GuidPartitionTable\GuidPartitionEntry.cs" />
-    <Compile Include="Disks\GuidPartitionTable\GuidPartitionEntryCollection.cs" />
-    <Compile Include="Disks\GuidPartitionTable\GuidPartitionTable.cs" />
-    <Compile Include="Disks\GuidPartitionTable\GuidPartitionTableHeader.cs" />
-    <Compile Include="Disks\MasterBootRecord\CHSAddress.cs" />
-    <Compile Include="Disks\MasterBootRecord\MasterBootRecord.cs" />
-    <Compile Include="Disks\MasterBootRecord\PartitionTableEntry.cs" />
-    <Compile Include="Disks\MasterBootRecord\PartitionTypeName.cs" />
     <Compile Include="Disks\RAMDisk.cs" />
     <Compile Include="Disks\RawDiskImage\RawDiskImage.cs" />
     <Compile Include="Disks\VHD\BlockAllocationTable.cs" />
@@ -101,6 +92,7 @@
     <Compile Include="FileSystems\NTFS\Structures\MftSegmentReference.cs" />
     <Compile Include="Helpers\BasicDiskHelper.cs" />
     <Compile Include="Helpers\DiskExtentHelper.cs" />
+    <Compile Include="Helpers\DiskExtentsHelper.cs" />
     <Compile Include="Helpers\ExtendHelper.Volume.cs" />
     <Compile Include="Helpers\Settings.cs" />
     <Compile Include="Helpers\VolumeHelper.cs" />
@@ -113,6 +105,7 @@
     <Compile Include="LogicalDiskManager\DatabaseRecords\VolumeRecord.cs" />
     <Compile Include="LogicalDiskManager\DiskGroupDatabase.cs" />
     <Compile Include="LogicalDiskManager\DynamicDisk.cs" />
+    <Compile Include="LogicalDiskManager\DynamicDiskExtent.cs" />
     <Compile Include="LogicalDiskManager\Enums\DatabaseHeaderUpdateStatus.cs" />
     <Compile Include="LogicalDiskManager\Enums\DatabaseRecordUpdateStatus.cs" />
     <Compile Include="LogicalDiskManager\Enums\ExtentLayoutName.cs" />
@@ -123,8 +116,12 @@
     <Compile Include="LogicalDiskManager\Exceptions\DatabaseNotFoundException.cs" />
     <Compile Include="LogicalDiskManager\Exceptions\MissingDatabaseRecordException.cs" />
     <Compile Include="LogicalDiskManager\Helpers\DynamicDiskExtentHelper.cs" />
+    <Compile Include="LogicalDiskManager\Helpers\DynamicDiskExtentsHelper.cs" />
     <Compile Include="LogicalDiskManager\Helpers\DynamicDiskHelper.cs" />
+    <Compile Include="LogicalDiskManager\Helpers\DynamicDiskHelper.Extents.cs" />
     <Compile Include="LogicalDiskManager\Helpers\DynamicVolumeHelper.cs" />
+    <Compile Include="LogicalDiskManager\Helpers\PublicRegionHelper.cs" />
+    <Compile Include="LogicalDiskManager\Helpers\PrivateRegionHelper.cs" />
     <Compile Include="LogicalDiskManager\Helpers\RetainHelper.cs" />
     <Compile Include="LogicalDiskManager\Helpers\VolumeManagerDatabaseHelper.cs" />
     <Compile Include="LogicalDiskManager\KernelUpdateLog\KernalUpdateLog.cs" />
@@ -141,6 +138,14 @@
     <Compile Include="LogicalDiskManager\Volumes\SimpleVolume.cs" />
     <Compile Include="LogicalDiskManager\Volumes\SpannedVolume.cs" />
     <Compile Include="LogicalDiskManager\Volumes\StripedVolume.cs" />
+    <Compile Include="PartitionTables\GuidPartitionTable\GuidPartitionEntry.cs" />
+    <Compile Include="PartitionTables\GuidPartitionTable\GuidPartitionEntryCollection.cs" />
+    <Compile Include="PartitionTables\GuidPartitionTable\GuidPartitionTable.cs" />
+    <Compile Include="PartitionTables\GuidPartitionTable\GuidPartitionTableHeader.cs" />
+    <Compile Include="PartitionTables\MasterBootRecord\CHSAddress.cs" />
+    <Compile Include="PartitionTables\MasterBootRecord\MasterBootRecord.cs" />
+    <Compile Include="PartitionTables\MasterBootRecord\PartitionTableEntry.cs" />
+    <Compile Include="PartitionTables\MasterBootRecord\PartitionTypeName.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Volumes\GPTPartition.cs" />
     <Compile Include="Volumes\MBRPartition.cs" />
@@ -148,13 +153,8 @@
     <Compile Include="Volumes\RemovableVolume.cs" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="Win32\Disks\DiskImage.Win32.cs" />
     <Compile Include="Win32\Disks\PhysicalDisk.cs" />
     <Compile Include="Win32\Disks\PhysicalDiskHandlePool.cs" />
-    <Compile Include="Win32\Disks\RawDiskImage\RawDiskImage.Win32.cs" />
-    <Compile Include="Win32\Disks\VHD\VirtualHardDisk.Win32.cs" />
-    <Compile Include="Win32\Disks\VMDK\SparseExtent.Win32.cs" />
-    <Compile Include="Win32\Disks\VMDK\VirtualMachineDisk.Win32.cs" />
     <Compile Include="Win32\Enums\Win32Error.cs" />
     <Compile Include="Win32\Helpers\DiskOfflineHelper.cs" />
     <Compile Include="Win32\Helpers\LockHelper.cs" />

+ 2 - 13
DiskAccessLibrary/Disks/DiskImage.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -14,9 +14,6 @@ namespace DiskAccessLibrary
 {
     public abstract partial class DiskImage : Disk
     {
-        // There is no way to specify sector size for IMG/VHD/VMDK.
-        public const int BytesPerDiskImageSector = 512;
-
         private string m_path;
 
         public DiskImage(string diskImagePath)
@@ -32,20 +29,12 @@ namespace DiskAccessLibrary
             }
         }
 
-        public abstract void Extend(long additionalNumberOfBytes);
+        public abstract void Extend(long numberOfAdditionalBytes);
 
         public abstract bool ExclusiveLock();
 
         public abstract bool ReleaseLock();
 
-        public override int BytesPerSector
-        {
-            get
-            {
-                return BytesPerDiskImageSector;
-            }
-        }
-
         public string Path
         {
             get

+ 119 - 19
DiskAccessLibrary/Disks/RawDiskImage/RawDiskImage.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -14,12 +14,28 @@ namespace DiskAccessLibrary
 {
     public partial class RawDiskImage : DiskImage
     {
+        public const int DefaultBytesPerSector = 512;
+
         const FileOptions FILE_FLAG_NO_BUFFERING = (FileOptions)0x20000000;
         private bool m_isExclusiveLock;
         private FileStream m_stream;
+        private int m_bytesPerSector;
+        private long m_size;
 
+        /// <exception cref="System.IO.IOException"></exception>
+        /// <exception cref="System.UnauthorizedAccessException"></exception>
         public RawDiskImage(string rawDiskImagePath) : base(rawDiskImagePath)
         {
+            m_bytesPerSector = DetectBytesPerSector(rawDiskImagePath);
+            m_size = new FileInfo(rawDiskImagePath).Length;
+        }
+
+        /// <exception cref="System.IO.IOException"></exception>
+        /// <exception cref="System.UnauthorizedAccessException"></exception>
+        public RawDiskImage(string rawDiskImagePath, int bytesPerSector) : base(rawDiskImagePath)
+        {
+            m_bytesPerSector = bytesPerSector;
+            m_size = new FileInfo(rawDiskImagePath).Length;
         }
 
         /// <exception cref="System.IO.IOException"></exception>
@@ -102,19 +118,42 @@ namespace DiskAccessLibrary
         }
 
         /// <exception cref="System.IO.IOException"></exception>
-        public override void Extend(long additionalNumberOfBytes)
+        public override void Extend(long numberOfAdditionalBytes)
         {
-            if (additionalNumberOfBytes % this.BytesPerSector > 0)
+            if (numberOfAdditionalBytes % this.BytesPerSector > 0)
             {
-                throw new ArgumentException("additionalNumberOfBytes must be a multiple of BytesPerSector");
+                throw new ArgumentException("numberOfAdditionalBytes must be a multiple of BytesPerSector");
             }
-
-            long length = this.Size;
+#if Win32
+            // calling AdjustTokenPrivileges and then immediately calling SetFileValidData will sometimes result in ERROR_PRIVILEGE_NOT_HELD.
+            // We can work around the issue by obtaining the privilege before obtaining the handle.
+            bool hasManageVolumePrivilege = SecurityUtils.ObtainManageVolumePrivilege();
+#endif
             if (!m_isExclusiveLock)
             {
                 m_stream = new FileStream(this.Path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 0x1000, FILE_FLAG_NO_BUFFERING | FileOptions.WriteThrough);
             }
-            m_stream.SetLength(length + additionalNumberOfBytes);
+            else
+            {
+                // Workaround for AdjustTokenPrivileges issue
+                ReleaseLock();
+                ExclusiveLock();
+            }
+
+            m_stream.SetLength(m_size + numberOfAdditionalBytes);
+            m_size += numberOfAdditionalBytes;
+#if Win32
+            if (hasManageVolumePrivilege)
+            {
+                try
+                {
+                    FileStreamUtils.SetValidLength(m_stream, m_size);
+                }
+                catch (IOException)
+                {
+                }
+            }
+#endif
             if (!m_isExclusiveLock)
             {
                 m_stream.Close();
@@ -125,27 +164,88 @@ namespace DiskAccessLibrary
         {
             get
             {
-                FileInfo info = new FileInfo(this.Path);
-                string[] components = info.Name.Split('.');
-                if (components.Length >= 3) // file.512.img
+                return m_bytesPerSector;
+            }
+        }
+
+        public override long Size
+        {
+            get
+            {
+                return m_size;
+            }
+        }
+
+        /// <param name="size">In bytes</param>
+        /// <exception cref="System.IO.IOException"></exception>
+        /// <exception cref="System.UnauthorizedAccessException"></exception>
+        public static RawDiskImage Create(string path, long size)
+        {
+            int bytesPerSector = DetectBytesPerSector(path);
+            return Create(path, size, bytesPerSector);
+        }
+
+        /// <param name="size">In bytes</param>
+        /// <exception cref="System.IO.IOException"></exception>
+        /// <exception cref="System.UnauthorizedAccessException"></exception>
+        internal static RawDiskImage Create(string path, long size, int bytesPerSector)
+        {
+            if (size % bytesPerSector > 0)
+            {
+                throw new ArgumentException("size must be a multiple of bytesPerSector");
+            }
+#if Win32
+            // calling AdjustTokenPrivileges and then immediately calling SetFileValidData will sometimes result in ERROR_PRIVILEGE_NOT_HELD.
+            // We can work around the issue by obtaining the privilege before obtaining the handle.
+            bool hasManageVolumePrivilege = SecurityUtils.ObtainManageVolumePrivilege();
+#endif
+            FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
+            try
+            {
+                stream.SetLength(size);
+            }
+            catch (IOException)
+            {
+                stream.Close();
+                try
+                {
+                    // Delete the incomplete file
+                    File.Delete(path);
+                }
+                catch (IOException)
+                {
+                }
+                throw;
+            }
+#if Win32
+            if (hasManageVolumePrivilege)
+            {
+                try
                 {
-                    string bytesPerSectorString = components[components.Length - 2];
-                    int bytesPerSector = Conversion.ToInt32(bytesPerSectorString, BytesPerDiskImageSector);
-                    return bytesPerSector;
+                    FileStreamUtils.SetValidLength(stream, size);
                 }
-                else
+                catch (IOException)
                 {
-                    return BytesPerDiskImageSector;
                 }
             }
+#endif
+            stream.Close();
+            return new RawDiskImage(path, bytesPerSector);
         }
 
-        public override long Size
+        public static int DetectBytesPerSector(string path)
         {
-            get
+            FileInfo info = new FileInfo(path);
+            string[] components = info.Name.Split('.');
+            if (components.Length >= 3) // file.512.img
+            {
+                string bytesPerSectorString = components[components.Length - 2];
+                int bytesPerSector = Conversion.ToInt32(bytesPerSectorString, DefaultBytesPerSector);
+                return bytesPerSector;
+            }
+            else
             {
-                FileInfo info = new FileInfo(this.Path);
-                return info.Length;
+                return DefaultBytesPerSector;
             }
         }
     }

+ 5 - 5
DiskAccessLibrary/Disks/VHD/BlockAllocationTable.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -40,7 +40,7 @@ namespace DiskAccessLibrary.VHD
         public byte[] GetBytes()
         {
             // The BAT is always extended to a sector boundary
-            int bufferLength = (int)Math.Ceiling((double)Entries.Length * 4 / VirtualHardDisk.BytesPerDiskImageSector) * VirtualHardDisk.BytesPerDiskImageSector;
+            int bufferLength = (int)Math.Ceiling((double)Entries.Length * 4 / VirtualHardDisk.BytesPerDiskSector) * VirtualHardDisk.BytesPerDiskSector;
             byte[] buffer = new byte[bufferLength];
             for (int index = 0; index < Entries.Length; index++)
             {
@@ -58,9 +58,9 @@ namespace DiskAccessLibrary.VHD
         public static BlockAllocationTable ReadBlockAllocationTable(string path, DynamicDiskHeader dynamicHeader)
         {
             uint maxTableEntries = dynamicHeader.MaxTableEntries;
-            long sectorIndex = (long)(dynamicHeader.TableOffset / VirtualHardDisk.BytesPerDiskImageSector);
-            int sectorCount = (int)Math.Ceiling((double)maxTableEntries * 4 / VirtualHardDisk.BytesPerDiskImageSector);
-            byte[] buffer = new RawDiskImage(path).ReadSectors(sectorIndex, sectorCount);
+            long sectorIndex = (long)(dynamicHeader.TableOffset / VirtualHardDisk.BytesPerDiskSector);
+            int sectorCount = (int)Math.Ceiling((double)maxTableEntries * 4 / VirtualHardDisk.BytesPerDiskSector);
+            byte[] buffer = new RawDiskImage(path, VirtualHardDisk.BytesPerDiskSector).ReadSectors(sectorIndex, sectorCount);
             return new BlockAllocationTable(buffer, maxTableEntries);
         }
     }

+ 25 - 50
DiskAccessLibrary/Disks/VHD/VirtualHardDisk.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -15,6 +15,9 @@ namespace DiskAccessLibrary
 {
     public partial class VirtualHardDisk : DiskImage, IDiskGeometry
     {
+        // VHD sector size is set to 512 bytes.
+        public const int BytesPerDiskSector = 512;
+
         private RawDiskImage m_file;
         private VHDFooter m_vhdFooter;
         // Dynamic VHD:
@@ -33,7 +36,7 @@ namespace DiskAccessLibrary
         public VirtualHardDisk(string virtualHardDiskPath) : base(virtualHardDiskPath)
         {
             // We can't read the VHD footer using this.ReadSector() because it's out of the disk boundaries
-            m_file = new RawDiskImage(virtualHardDiskPath);
+            m_file = new RawDiskImage(virtualHardDiskPath, BytesPerDiskSector);
             byte[] buffer = m_file.ReadSector(m_file.Size / m_file.BytesPerSector - 1);
             m_vhdFooter = new VHDFooter(buffer);
 
@@ -131,20 +134,20 @@ namespace DiskAccessLibrary
             m_file.WriteSectors(sectorIndex, data);
         }
 
-        public override void Extend(long additionalNumberOfBytes)
+        public override void Extend(long numberOfAdditionalBytes)
         {
-            if (additionalNumberOfBytes % this.BytesPerSector > 0)
+            if (numberOfAdditionalBytes % this.BytesPerSector > 0)
             {
-                throw new ArgumentException("additionalNumberOfBytes must be a multiple of BytesPerSector");
+                throw new ArgumentException("numberOfAdditionalBytes must be a multiple of BytesPerSector");
             }
 
             if (m_vhdFooter.DiskType == VirtualHardDiskType.Fixed)
             {
                 long length = this.Size; // does not include the footer
-                m_file.Extend(additionalNumberOfBytes);
-                m_vhdFooter.CurrentSize += (ulong)additionalNumberOfBytes;
+                m_file.Extend(numberOfAdditionalBytes);
+                m_vhdFooter.CurrentSize += (ulong)numberOfAdditionalBytes;
                 byte[] footerBytes = m_vhdFooter.GetBytes();
-                m_file.WriteSectors((length + additionalNumberOfBytes) / this.BytesPerSector, footerBytes);
+                m_file.WriteSectors((length + numberOfAdditionalBytes) / this.BytesPerSector, footerBytes);
             }
             else
             {
@@ -176,6 +179,14 @@ namespace DiskAccessLibrary
             }
         }
 
+        public override int BytesPerSector
+        {
+            get
+            {
+                return BytesPerDiskSector;
+            }
+        }
+
         public override long Size
         {
             get
@@ -240,56 +251,20 @@ namespace DiskAccessLibrary
             cylinders = (ushort)(cylindersTimesHeads / heads);
         }
 
-        /// <param name="length">In bytes</param>
+        /// <param name="size">In bytes</param>
         /// <exception cref="System.IO.IOException"></exception>
         /// <exception cref="System.UnauthorizedAccessException"></exception>
         public static VirtualHardDisk Create(string path, long size)
         {
-#if Win32
-            // calling AdjustTokenPrivileges and then immediately calling SetFileValidData will sometimes result in ERROR_PRIVILEGE_NOT_HELD.
-            // We can work around the issue by obtaining the privilege before obtaining the handle.
-            bool hasManageVolumePrivilege = SecurityUtils.ObtainManageVolumePrivilege();
-#endif
-            FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
-            try
-            {
-                stream.SetLength(size + 512); // VHD footer is 512 bytes
-            }
-            catch (IOException)
-            {
-                stream.Close();
-                try
-                {
-                    // Delete the incomplete file
-                    File.Delete(path);
-                }
-                catch (IOException)
-                {
-                }
-                throw;
-            }
-
-#if Win32
-            if (hasManageVolumePrivilege)
-            {
-                try
-                {
-                    FileStreamUtils.SetValidLength(stream, size + 512);
-                }
-                catch (IOException)
-                {
-                }
-            }
-#endif
-
             VHDFooter footer = new VHDFooter();
             footer.OriginalSize = (ulong)size;
             footer.CurrentSize = (ulong)size;
             footer.SetCurrentTimeStamp();
-            footer.SetDiskGeometry((ulong)size / DiskImage.BytesPerDiskImageSector);
-            stream.Seek(size, SeekOrigin.Begin);
-            stream.Write(footer.GetBytes(), 0, VHDFooter.Length);
-            stream.Close();
+            footer.SetDiskGeometry((ulong)size / BytesPerDiskSector);
+
+            RawDiskImage diskImage = RawDiskImage.Create(path, size + VHDFooter.Length, BytesPerDiskSector);
+            diskImage.WriteSectors(size / BytesPerDiskSector, footer.GetBytes());
+
             return new VirtualHardDisk(path);
         }
     }

+ 11 - 3
DiskAccessLibrary/Disks/VMDK/SparseExtent.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -21,7 +21,7 @@ namespace DiskAccessLibrary.VMDK
 
         public SparseExtent(string path) : base(path)
         {
-            m_file = new RawDiskImage(path);
+            m_file = new RawDiskImage(path, VirtualMachineDisk.BytesPerDiskSector);
             byte[] headerBytes = m_file.ReadSector(0);
             m_header = new SparseExtentHeader(headerBytes);
 
@@ -121,11 +121,19 @@ namespace DiskAccessLibrary.VMDK
             throw new NotImplementedException("The method or operation is not implemented.");
         }
 
-        public override void Extend(long additionalNumberOfBytes)
+        public override void Extend(long numberOfAdditionalBytes)
         {
             throw new NotImplementedException("The method or operation is not implemented.");
         }
 
+        public override int BytesPerSector
+        {
+            get
+            {
+                return VirtualMachineDisk.BytesPerDiskSector;
+            }
+        }
+
         public override long Size
         {
             get

+ 9 - 6
DiskAccessLibrary/Disks/VMDK/VirtualMachineDisk.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -15,6 +15,9 @@ namespace DiskAccessLibrary
 {
     public partial class VirtualMachineDisk : DiskImage, IDiskGeometry
     {
+        // VMDK sector size is set to 512 bytes.
+        public const int BytesPerDiskSector = 512;
+
         private const uint BaseDiskParentCID = 0xffffffff;
 
         private string m_descriptorPath;
@@ -89,7 +92,7 @@ namespace DiskAccessLibrary
             {
                 VirtualMachineDiskExtentEntry entry = m_descriptor.ExtentEntries[0];
                 string directory = System.IO.Path.GetDirectoryName(descriptorPath);
-                DiskImage extent = new RawDiskImage(directory + @"\" + entry.FileName);
+                DiskImage extent = new RawDiskImage(directory + @"\" + entry.FileName, BytesPerDiskSector);
                 m_extent = extent;
             }
         }
@@ -118,17 +121,17 @@ namespace DiskAccessLibrary
             m_extent.WriteSectors(sectorIndex, data);
         }
 
-        public override void Extend(long additionalNumberOfBytes)
+        public override void Extend(long numberOfAdditionalBytes)
         {
             if (m_descriptor.DiskType == VirtualMachineDiskType.MonolithicFlat)
             {
                 // Add updated extent entries
                 List<string> lines = VirtualMachineDiskDescriptor.ReadASCIITextLines(m_descriptorPath);
-                m_descriptor.ExtentEntries[0].SizeInSectors += additionalNumberOfBytes / this.BytesPerSector;
+                m_descriptor.ExtentEntries[0].SizeInSectors += numberOfAdditionalBytes / this.BytesPerSector;
                 m_descriptor.UpdateExtentEntries(lines);
 
                 File.WriteAllLines(m_descriptorPath, lines.ToArray(), Encoding.ASCII);
-                ((DiskImage)m_extent).Extend(additionalNumberOfBytes);
+                ((DiskImage)m_extent).Extend(numberOfAdditionalBytes);
             }
             else
             {
@@ -140,7 +143,7 @@ namespace DiskAccessLibrary
         {
             get
             {
-                return DiskImage.BytesPerDiskImageSector;
+                return BytesPerDiskSector;
             }
         }
 

+ 1 - 1
DiskAccessLibrary/FileSystems/IExtendableFileSystem.cs

@@ -15,6 +15,6 @@ namespace DiskAccessLibrary.FileSystems
     {
         /// <returns>In bytes</returns>
         long GetMaximumSizeToExtend();
-        void Extend(long additionalNumberOfSectors);
+        void Extend(long numberOfAdditionalSectors);
     }
 }

+ 2 - 2
DiskAccessLibrary/FileSystems/NTFS/Adapters/NTFSFileSystem.cs

@@ -162,9 +162,9 @@ namespace DiskAccessLibrary.FileSystems.NTFS
             return m_volume.GetMaximumSizeToExtend();
         }
 
-        public void Extend(long additionalNumberOfSectors)
+        public void Extend(long numberOfAdditionalSectors)
         {
-            m_volume.Extend(additionalNumberOfSectors);
+            m_volume.Extend(numberOfAdditionalSectors);
         }
 
         public override string Name

+ 13 - 13
DiskAccessLibrary/FileSystems/NTFS/NTFSVolume.Extend.cs

@@ -19,21 +19,21 @@ namespace DiskAccessLibrary.FileSystems.NTFS
             return m_volume.Size - (this.Size + m_volume.BytesPerSector);
         }
 
-        public void Extend(long additionalNumberOfSectors)
+        public void Extend(long numberOfAdditionalSectors)
         {
-            Extend((ulong)additionalNumberOfSectors);
+            Extend((ulong)numberOfAdditionalSectors);
         }
 
-        public void Extend(ulong additionalNumberOfSectors)
+        public void Extend(ulong numberOfAdditionalSectors)
         {
             ulong originalNumberOfSectors = m_bootRecord.TotalSectors;
             ulong currentNumberOfClusters = m_bootRecord.TotalSectors / m_bootRecord.SectorsPerCluster;
-            ulong additionalNumberOfClusters = additionalNumberOfSectors / m_bootRecord.SectorsPerCluster;
+            ulong numberOfAdditionalClusters = numberOfAdditionalSectors / m_bootRecord.SectorsPerCluster;
 
-            Extend(currentNumberOfClusters, additionalNumberOfClusters);
+            Extend(currentNumberOfClusters, numberOfAdditionalClusters);
 
             // We set TotalSectors only after extending the File system, or otherwise the $bitmap size will mismatch
-            m_bootRecord.TotalSectors += additionalNumberOfClusters * m_bootRecord.SectorsPerCluster; // we only add usable sectors
+            m_bootRecord.TotalSectors += numberOfAdditionalClusters * m_bootRecord.SectorsPerCluster; // we only add usable sectors
 
             // update boot sector
             byte[] bootRecordBytes = m_bootRecord.GetBytes();
@@ -41,12 +41,12 @@ namespace DiskAccessLibrary.FileSystems.NTFS
 
             // recreate the backup boot sector at the new end of the raw volume
             // Note: The backup boot sector does not count as part of the NTFS volume
-            long backupBootSectorIndex = (long)(originalNumberOfSectors + additionalNumberOfSectors);
+            long backupBootSectorIndex = (long)(originalNumberOfSectors + numberOfAdditionalSectors);
             WriteSectors(backupBootSectorIndex, bootRecordBytes);
         }
 
         // Note: there could be up to 2^64 clusters ( http://technet.microsoft.com/en-us/library/cc938432.aspx ) 
-        private void Extend(ulong currentNumberOfClusters, ulong additionalNumberOfClusters)
+        private void Extend(ulong currentNumberOfClusters, ulong numberOfAdditionalClusters)
         {
             // Each bit in the $Bitmap file represents a cluster.
             // The size of the $Bitmap file is always a multiple of 8 bytes, extra bits are always set to 1.
@@ -60,7 +60,7 @@ namespace DiskAccessLibrary.FileSystems.NTFS
 
             if (currentNumberOfClusters % 64 > 0)
             {
-                ulong numberOfClustersToAllocate = additionalNumberOfClusters - (64 - (currentNumberOfClusters % 64));
+                ulong numberOfClustersToAllocate = numberOfAdditionalClusters - (64 - (currentNumberOfClusters % 64));
                 ulong numberOfBytesToAllocate = (ulong)Math.Ceiling((double)numberOfClustersToAllocate / 8);
                 numberOfBytesToAllocate = (ulong)Math.Ceiling((double)numberOfBytesToAllocate / 8) * 8;
 
@@ -86,11 +86,11 @@ namespace DiskAccessLibrary.FileSystems.NTFS
             }
             else
             {
-                ulong additionalNumberOfBytes = (ulong)Math.Ceiling((double)additionalNumberOfClusters / 8);
-                additionalNumberOfBytes = (ulong)Math.Ceiling((double)additionalNumberOfBytes / 8) * 8;
+                ulong numberOfAdditionalBytes = (ulong)Math.Ceiling((double)numberOfAdditionalClusters / 8);
+                numberOfAdditionalBytes = (ulong)Math.Ceiling((double)numberOfAdditionalBytes / 8) * 8;
 
-                bitmap = new byte[additionalNumberOfBytes];
-                nextClusterIndexInBitmap = additionalNumberOfClusters;
+                bitmap = new byte[numberOfAdditionalBytes];
+                nextClusterIndexInBitmap = numberOfAdditionalClusters;
             }
 
             // mark extra bits as used:

+ 42 - 1
DiskAccessLibrary/Helpers/BasicDiskHelper.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -13,6 +13,13 @@ namespace DiskAccessLibrary
 {
     public class BasicDiskHelper
     {
+        /// <summary>
+        /// While technically a partition on an MBR disk could start on sector 1, we use sector 64 for alignment purposes.
+        /// Note: Windows 7 will start MBR partitions on sector 128 for disks with 512-byte sectors, and on sector 64 for disks with 1KB/2KB/4KB sectors.
+        /// Windows will start the dynamic data partition of a dynamic disks on sector 63 (or 1 in some cases), but the volume (extent) itself may be aligned to native sector boundaries.
+        /// </summary>
+        public const int MBRDiskFirstUsableSector = 64;
+
         public static List<Partition> GetPartitions(Disk disk)
         {
             List<Partition> result = new List<Partition>();
@@ -62,5 +69,39 @@ namespace DiskAccessLibrary
             return null;
         }
 
+        public static List<DiskExtent> GetUnallocatedExtents(Disk disk)
+        {
+            MasterBootRecord mbr = MasterBootRecord.ReadFromDisk(disk);
+            List<DiskExtent> result = new List<DiskExtent>();
+            if (mbr == null)
+            {
+                result.Add(new DiskExtent(disk, 0, disk.Size));
+                return result;
+            }
+            else
+            {
+                long dataRegionStartSector;
+                long dataRegionSize;
+                if (!mbr.IsGPTBasedDisk)
+                {
+                    dataRegionStartSector = MBRDiskFirstUsableSector;
+                    dataRegionSize = Math.Min(disk.Size, UInt32.MaxValue * disk.BytesPerSector) - dataRegionStartSector;
+                }
+                else
+                {
+                    GuidPartitionTableHeader gptHeader = GuidPartitionTableHeader.ReadFromDisk(disk);
+                    dataRegionStartSector = (long)gptHeader.FirstUsableLBA;
+                    dataRegionSize = (long)(gptHeader.LastUsableLBA - gptHeader.FirstUsableLBA + 1) * disk.BytesPerSector;
+                }
+
+                List<Partition> partitions = GetPartitions(disk);
+                List<DiskExtent> usedExtents = new List<DiskExtent>();
+                foreach (Partition partition in partitions)
+                {
+                    usedExtents.Add(partition.Extent);
+                }
+                return DiskExtentsHelper.GetUnallocatedExtents(disk, dataRegionStartSector, dataRegionSize, usedExtents);
+            }
+        }
     }
 }

+ 59 - 0
DiskAccessLibrary/Helpers/DiskExtentsHelper.cs

@@ -0,0 +1,59 @@
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DiskAccessLibrary
+{
+    public class DiskExtentsHelper
+    {
+        /// <param name="dataRegionSize">In bytes</param>
+        internal static List<DiskExtent> GetUnallocatedExtents(Disk disk, long dataRegionStartSector, long dataRegionSize, List<DiskExtent> usedExtents)
+        {
+            List<DiskExtent> result = new List<DiskExtent>();
+            long startSector = dataRegionStartSector;
+            SortExtentsByFirstSector(usedExtents);
+            // see if there is room before each extent
+            foreach (DiskExtent extent in usedExtents)
+            {
+                long extentStartSector = extent.FirstSector;
+                long nextStartSector = extent.FirstSector + extent.Size / disk.BytesPerSector;
+                long freeSpaceInBytes = (extentStartSector - startSector) * disk.BytesPerSector;
+                if (freeSpaceInBytes > 0)
+                {
+                    result.Add(new DiskExtent(disk, startSector, freeSpaceInBytes));
+                }
+
+                startSector = nextStartSector;
+            }
+
+            // see if there is room after the last extent
+            long spaceInBytes = dataRegionSize - (startSector - dataRegionStartSector) * disk.BytesPerSector;
+            if (spaceInBytes > 0)
+            {
+                result.Add(new DiskExtent(disk, startSector, spaceInBytes));
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// Sort (in-place) extents by first sector
+        /// </summary>
+        public static void SortExtentsByFirstSector(List<DiskExtent> extents)
+        {
+            SortedList<long, DiskExtent> list = new SortedList<long, DiskExtent>();
+            foreach (DiskExtent extent in extents)
+            {
+                list.Add(extent.FirstSector, extent);
+            }
+
+            extents.Clear();
+            extents.AddRange(list.Values);
+        }
+    }
+}

+ 16 - 16
DiskAccessLibrary/Helpers/ExtendHelper.Volume.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -133,7 +133,7 @@ namespace DiskAccessLibrary
         {
             DynamicDisk disk = DynamicDisk.ReadFromDisk(targetExtent.Disk);
             PrivateHeader privateHeader = disk.PrivateHeader;
-            List<DynamicDiskExtent> extents = DynamicDiskExtentHelper.GetDiskExtents(disk);
+            List<DynamicDiskExtent> extents = DynamicDiskHelper.GetDiskExtents(disk);
             if (extents == null)
             {
                 throw new InvalidDataException("Cannot read extents information from disk");
@@ -153,52 +153,52 @@ namespace DiskAccessLibrary
             return max;
         }
 
-        public static void ExtendVolume(Volume volume, long additionalNumberOfExtentSectors, DiskGroupDatabase database)
+        public static void ExtendVolume(Volume volume, long numberOfAdditionalExtentSectors, DiskGroupDatabase database)
         {
             if (volume is Partition)
             {
-                ExtendPartition((Partition)volume, additionalNumberOfExtentSectors);
+                ExtendPartition((Partition)volume, numberOfAdditionalExtentSectors);
             }
             else if (volume is DynamicVolume)
             {
-                ExtendDynamicVolume((DynamicVolume)volume, additionalNumberOfExtentSectors, database);
+                ExtendDynamicVolume((DynamicVolume)volume, numberOfAdditionalExtentSectors, database);
             }
         }
 
-        public static void ExtendPartition(Partition volume, long additionalNumberOfExtentSectors)
+        public static void ExtendPartition(Partition volume, long numberOfAdditionalExtentSectors)
         {
             if (volume is MBRPartition)
             {
                 MBRPartition partition = (MBRPartition)volume;
-                ExtendMBRPartition(partition, additionalNumberOfExtentSectors);
+                ExtendMBRPartition(partition, numberOfAdditionalExtentSectors);
             }
             else if (volume is GPTPartition)
             {
                 GPTPartition partition = (GPTPartition)volume;
-                ExtendGPTPartition(partition, additionalNumberOfExtentSectors);
+                ExtendGPTPartition(partition, numberOfAdditionalExtentSectors);
             }
         }
 
-        public static void ExtendDynamicVolume(DynamicVolume volume, long additionalNumberOfExtentSectors, DiskGroupDatabase database)
+        public static void ExtendDynamicVolume(DynamicVolume volume, long numberOfAdditionalExtentSectors, DiskGroupDatabase database)
         {
             if (volume is SimpleVolume)
             {
                 SimpleVolume simpleVolume = (SimpleVolume)volume;
-                VolumeManagerDatabaseHelper.ExtendSimpleVolume(database, simpleVolume, additionalNumberOfExtentSectors);
+                VolumeManagerDatabaseHelper.ExtendSimpleVolume(database, simpleVolume, numberOfAdditionalExtentSectors);
             }
             else if (volume is StripedVolume)
             {
                 StripedVolume stripedVolume = (StripedVolume)volume;
-                VolumeManagerDatabaseHelper.ExtendStripedVolume(database, stripedVolume, additionalNumberOfExtentSectors);
+                VolumeManagerDatabaseHelper.ExtendStripedVolume(database, stripedVolume, numberOfAdditionalExtentSectors);
             }
             else if (volume is Raid5Volume)
             {
                 Raid5Volume raid5Volume = (Raid5Volume)volume;
-                VolumeManagerDatabaseHelper.ExtendRAID5Volume(database, raid5Volume, additionalNumberOfExtentSectors);
+                VolumeManagerDatabaseHelper.ExtendRAID5Volume(database, raid5Volume, numberOfAdditionalExtentSectors);
             }
         }
 
-        public static void ExtendMBRPartition(MBRPartition partition, long additionalNumberOfSectors)
+        public static void ExtendMBRPartition(MBRPartition partition, long numberOfAdditionalExtentSectors)
         {
             Disk disk = partition.Disk;
             MasterBootRecord mbr = MasterBootRecord.ReadFromDisk(disk);
@@ -206,7 +206,7 @@ namespace DiskAccessLibrary
             {
                 if (mbr.PartitionTable[index].FirstSectorLBA == partition.FirstSector)
                 {
-                    mbr.PartitionTable[index].SectorCountLBA += (uint)additionalNumberOfSectors;
+                    mbr.PartitionTable[index].SectorCountLBA += (uint)numberOfAdditionalExtentSectors;
                     ulong lastSectorLBA = mbr.PartitionTable[index].LastSectorLBA;
                     mbr.PartitionTable[index].LastSectorCHS = CHSAddress.FromLBA(lastSectorLBA, disk);
                     break;
@@ -215,7 +215,7 @@ namespace DiskAccessLibrary
             MasterBootRecord.WriteToDisk(disk, mbr);
         }
 
-        public static void ExtendGPTPartition(GPTPartition partition, long additionalNumberOfSectors)
+        public static void ExtendGPTPartition(GPTPartition partition, long numberOfAdditionalExtentSectors)
         {
             Disk disk = partition.Disk;
             GuidPartitionTableHeader primaryHeader = GuidPartitionTableHeader.ReadPrimaryFromDisk(disk);
@@ -236,7 +236,7 @@ namespace DiskAccessLibrary
             {
                 if ((long)entry.FirstLBA == partition.FirstSector)
                 {
-                    entry.LastLBA += (ulong)additionalNumberOfSectors;
+                    entry.LastLBA += (ulong)numberOfAdditionalExtentSectors;
                     GuidPartitionEntry.WriteToDisk(disk, primaryHeader, entry);
                     GuidPartitionEntry.WriteToDisk(disk, secondaryHeader, entry);
                     break;

DiskAccessLibrary/DiskExtent/DynamicDiskExtent.cs → DiskAccessLibrary/LogicalDiskManager/DynamicDiskExtent.cs


+ 10 - 214
DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicDiskExtentHelper.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -38,23 +38,21 @@ namespace DiskAccessLibrary.LogicalDiskManager
             }
         }
 
-
         /// <summary>
         /// Support null disks
         /// </summary>
         public static DynamicDiskExtent GetDiskExtent(DynamicDisk dynamicDisk, ExtentRecord extentRecord)
         {
-            ulong extentStartSector = GetExtentStartSector(dynamicDisk, extentRecord);
-            int bytesPerSector = 512; // default for missing disk
+            long extentStartSector = GetExtentStartSector(dynamicDisk, extentRecord);
+            long extentSize = (long)extentRecord.SizeLBA * PublicRegionHelper.BytesPerPublicRegionSector;
             Disk disk = null;
             Guid diskGuid = Guid.Empty;
             if (dynamicDisk != null)
             {
-                bytesPerSector = dynamicDisk.BytesPerSector;
                 disk = dynamicDisk.Disk;
                 diskGuid = dynamicDisk.DiskGuid;
             }
-            DynamicDiskExtent extent = new DynamicDiskExtent(disk, (long)extentStartSector, (long)extentRecord.SizeLBA * bytesPerSector, extentRecord.ExtentId);
+            DynamicDiskExtent extent = new DynamicDiskExtent(disk, extentStartSector, extentSize, extentRecord.ExtentId);
             extent.Name = extentRecord.Name;
             extent.DiskGuid = diskGuid;
             return extent;
@@ -63,219 +61,17 @@ namespace DiskAccessLibrary.LogicalDiskManager
         /// <summary>
         /// Support null disks
         /// </summary>
-        public static ulong GetExtentStartSector(DynamicDisk disk, ExtentRecord extentRecord)
+        public static long GetExtentStartSector(DynamicDisk disk, ExtentRecord extentRecord)
         {
-            ulong dataStartLBA = 0;
+            long publicRegionStartLBA = 0;
+            int bytesPerDiskSector = DynamicColumn.DefaultBytesPerSector; // default for missing disks
             if (disk != null)
             {
+                bytesPerDiskSector = disk.BytesPerSector;
                 PrivateHeader privateHeader = disk.PrivateHeader;
-                dataStartLBA = privateHeader.PublicRegionStartLBA;
-            }
-            ulong extentStartSector = dataStartLBA + extentRecord.DiskOffsetLBA;
-            return extentStartSector;
-        }
-
-        /// <param name="targetOffset">in bytes</param>
-        public static bool IsMoveLocationValid(DynamicDisk disk, DynamicDiskExtent sourceExtent, long targetOffset)
-        {
-            List<DynamicDiskExtent> extents = GetDiskExtents(disk);
-            // extents are sorted by first sector
-            if (extents == null)
-            {
-                return false;
-            }
-
-            PrivateHeader privateHeader = disk.PrivateHeader;
-            if (targetOffset % privateHeader.BytesPerSector > 0)
-            {
-                return false;
-            }
-
-            int index = GetIndexOfExtentID(extents, sourceExtent.ExtentID);
-            extents.RemoveAt(index);
-
-            long targetStartSector = targetOffset / disk.BytesPerSector;
-
-            long publicRegionStartSector = (long)privateHeader.PublicRegionStartLBA;
-            long startSector = publicRegionStartSector;
-            long publicRegionSizeLBA = (long)privateHeader.PublicRegionSizeLBA;
-
-            if (targetStartSector < publicRegionStartSector)
-            {
-                return false;
-            }
-
-            if (targetStartSector + sourceExtent.TotalSectors > publicRegionStartSector + publicRegionSizeLBA)
-            {
-                return false;
-            }
-            
-            foreach (DynamicDiskExtent extent in extents)
-            {
-                long extentStartSector = extent.FirstSector;
-                long extentEndSector = extent.FirstSector + extent.Size / disk.BytesPerSector - 1;
-                if (extentStartSector >= targetStartSector &&
-                    extentStartSector <= targetStartSector + sourceExtent.TotalSectors)
-                {
-                    // extent start within the requested region
-                    return false;
-                }
-
-                if (extentEndSector >= targetStartSector &&
-                    extentEndSector <= targetStartSector + sourceExtent.TotalSectors)
-                {
-                    // extent end within the requested region
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        public static DiskExtent AllocateNewExtent(DynamicDisk disk, long allocationLength)
-        {
-            return AllocateNewExtent(disk, allocationLength, 0);
-        }
-
-        /// <param name="allocationLength">In bytes</param>
-        /// <param name="alignInSectors">0 or 1 for no alignment</param>
-        /// <returns>Allocated DiskExtent or null if there is not enough free disk space</returns>
-        public static DiskExtent AllocateNewExtent(DynamicDisk disk, long allocationLength, long alignInSectors)
-        {
-            List<DiskExtent> unallocatedExtents = GetUnallocatedSpace(disk);
-            if (unallocatedExtents == null)
-            {
-                return null;
-            }
-
-            for (int index = 0; index < unallocatedExtents.Count; index++)
-            {
-                DiskExtent extent = unallocatedExtents[index];
-                if (alignInSectors > 1)
-                {
-                    extent = DiskExtentHelper.GetAlignedDiskExtent(extent, alignInSectors);
-                }
-                if (extent.Size >= allocationLength)
-                {
-                    return new DiskExtent(extent.Disk, extent.FirstSector, allocationLength);
-                }
-            }
-            return null;
-        }
-
-        public static long GetMaxNewExtentLength(DynamicDisk disk)
-        {
-            return GetMaxNewExtentLength(disk, 0);
-        }
-
-        /// <returns>In bytes</returns>
-        public static long GetMaxNewExtentLength(DynamicDisk disk, long alignInSectors)
-        {
-            List<DiskExtent> unallocatedExtents = GetUnallocatedSpace(disk);
-            if (unallocatedExtents == null)
-            {
-                return -1;
-            }
-
-            long result = 0;
-            for(int index = 0; index < unallocatedExtents.Count; index++)
-            {
-                DiskExtent extent = unallocatedExtents[index];
-                if (alignInSectors > 1)
-                {
-                    extent = DiskExtentHelper.GetAlignedDiskExtent(extent, alignInSectors);
-                }
-                if (extent.Size > result)
-                {
-                    result = extent.Size;
-                }
-            }
-            return result;
-        }
-
-        private static List<DiskExtent> GetUnallocatedSpace(DynamicDisk disk)
-        {
-            List<DynamicDiskExtent> extents = GetDiskExtents(disk);
-            // extents are sorted by first sector
-            if (extents == null)
-            {
-                return null;
-            }
-
-            List<DiskExtent> result = new List<DiskExtent>();
-
-            PrivateHeader privateHeader = disk.PrivateHeader;
-            long publicRegionStartSector = (long)privateHeader.PublicRegionStartLBA;
-            long startSector = publicRegionStartSector;
-            long publicRegionSize = (long)privateHeader.PublicRegionSizeLBA * disk.Disk.BytesPerSector;
-
-            // see if there is room before each extent
-            foreach (DynamicDiskExtent extent in extents)
-            {
-                long extentStartSector = extent.FirstSector;
-                long nextStartSector = extent.FirstSector + extent.Size / disk.BytesPerSector;
-                long freeSpaceInBytes = (extentStartSector - startSector) * disk.BytesPerSector;
-                if (freeSpaceInBytes > 0)
-                {
-                    result.Add(new DiskExtent(disk.Disk, startSector, freeSpaceInBytes));
-                }
-
-                startSector = nextStartSector;
-            }
-
-            // see if there is room after the last extent
-            long spaceInBytes = publicRegionSize - (startSector - publicRegionStartSector) * disk.Disk.BytesPerSector;
-            if (spaceInBytes > 0)
-            {
-                result.Add(new DiskExtent(disk.Disk, startSector, spaceInBytes));
-            }
-
-            return result;
-        }
-
-        /// <summary>
-        /// Sorted by first sector
-        /// </summary>
-        /// <returns>null if there was a problem reading extent information from disk</returns>
-        public static List<DynamicDiskExtent> GetDiskExtents(DynamicDisk disk)
-        {
-            List<DynamicDiskExtent> result = new List<DynamicDiskExtent>();
-            PrivateHeader privateHeader = disk.PrivateHeader;
-            if (privateHeader != null)
-            {
-                VolumeManagerDatabase database = VolumeManagerDatabase.ReadFromDisk(disk);
-                if (database != null)
-                {
-                    DiskRecord diskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid);
-                    List<ExtentRecord> extentRecords = database.FindExtentsByDiskID(diskRecord.DiskId);
-                    foreach (ExtentRecord extentRecord in extentRecords)
-                    {
-                        DynamicDiskExtent extent = GetDiskExtent(disk, extentRecord);
-                        result.Add(extent);
-                    }
-                    SortExtentsByFirstSector(result);
-                    return result;
-                }
-            }
-            return null;
-        }
-
-        /// <summary>
-        /// Sort (in-place) extents by first sector
-        /// </summary>
-        public static void SortExtentsByFirstSector(List<DynamicDiskExtent> extents)
-        {
-            SortedList<long, DynamicDiskExtent> list = new SortedList<long, DynamicDiskExtent>();
-            foreach (DynamicDiskExtent extent in extents)
-            {
-                list.Add(extent.FirstSector, extent);
-            }
-
-            extents.Clear();
-            foreach (DynamicDiskExtent extent in list.Values)
-            {
-                extents.Add(extent);
+                publicRegionStartLBA = (long)privateHeader.PublicRegionStartLBA;
             }
+            return PublicRegionHelper.TranslateFromPublicRegionLBA((long)extentRecord.DiskOffsetLBA, publicRegionStartLBA, bytesPerDiskSector);
         }
     }
 }

+ 30 - 0
DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicDiskExtentsHelper.cs

@@ -0,0 +1,30 @@
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DiskAccessLibrary.LogicalDiskManager
+{
+    public class DynamicDiskExtentsHelper
+    {
+        /// <summary>
+        /// Sort (in-place) extents by first sector
+        /// </summary>
+        public static void SortExtentsByFirstSector(List<DynamicDiskExtent> extents)
+        {
+            SortedList<long, DynamicDiskExtent> list = new SortedList<long, DynamicDiskExtent>();
+            foreach (DynamicDiskExtent extent in extents)
+            {
+                list.Add(extent.FirstSector, extent);
+            }
+
+            extents.Clear();
+            extents.AddRange(list.Values);
+        }
+    }
+}

+ 185 - 0
DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicDiskHelper.Extents.cs

@@ -0,0 +1,185 @@
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Utilities;
+
+namespace DiskAccessLibrary.LogicalDiskManager
+{
+    public partial class DynamicDiskHelper
+    {
+        public static List<DiskExtent> GetUnallocatedExtents(DynamicDisk disk)
+        {
+            List<DynamicDiskExtent> extents = GetDiskExtents(disk);
+            // extents are sorted by first sector
+            if (extents == null)
+            {
+                return null;
+            }
+
+            List<DiskExtent> result = new List<DiskExtent>();
+
+            PrivateHeader privateHeader = disk.PrivateHeader;
+            long publicRegionStartSector = (long)privateHeader.PublicRegionStartLBA;
+            long startSector = publicRegionStartSector;
+            long publicRegionSize = (long)privateHeader.PublicRegionSizeLBA * disk.Disk.BytesPerSector;
+
+            // see if there is room before each extent
+            foreach (DynamicDiskExtent extent in extents)
+            {
+                long extentStartSector = extent.FirstSector;
+                long nextStartSector = extent.FirstSector + extent.Size / disk.BytesPerSector;
+                long freeSpaceInBytes = (extentStartSector - startSector) * disk.BytesPerSector;
+                if (freeSpaceInBytes > 0)
+                {
+                    result.Add(new DiskExtent(disk.Disk, startSector, freeSpaceInBytes));
+                }
+
+                startSector = nextStartSector;
+            }
+
+            // see if there is room after the last extent
+            long spaceInBytes = publicRegionSize - (startSector - publicRegionStartSector) * disk.Disk.BytesPerSector;
+            if (spaceInBytes > 0)
+            {
+                result.Add(new DiskExtent(disk.Disk, startSector, spaceInBytes));
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Sorted by first sector
+        /// </summary>
+        /// <returns>null if there was a problem reading extent information from disk</returns>
+        public static List<DynamicDiskExtent> GetDiskExtents(DynamicDisk disk)
+        {
+            List<DynamicDiskExtent> result = new List<DynamicDiskExtent>();
+            PrivateHeader privateHeader = disk.PrivateHeader;
+            if (privateHeader != null)
+            {
+                VolumeManagerDatabase database = VolumeManagerDatabase.ReadFromDisk(disk);
+                if (database != null)
+                {
+                    DiskRecord diskRecord = database.FindDiskByDiskGuid(privateHeader.DiskGuid);
+                    List<ExtentRecord> extentRecords = database.FindExtentsByDiskID(diskRecord.DiskId);
+                    foreach (ExtentRecord extentRecord in extentRecords)
+                    {
+                        DynamicDiskExtent extent = DynamicDiskExtentHelper.GetDiskExtent(disk, extentRecord);
+                        result.Add(extent);
+                    }
+                    DynamicDiskExtentsHelper.SortExtentsByFirstSector(result);
+                    return result;
+                }
+            }
+            return null;
+        }
+
+        public static long GetMaxNewExtentLength(DynamicDisk disk)
+        {
+            return GetMaxNewExtentLength(disk, 0);
+        }
+
+        /// <returns>In bytes</returns>
+        public static long GetMaxNewExtentLength(DynamicDisk disk, long alignInSectors)
+        {
+            List<DiskExtent> unallocatedExtents = GetUnallocatedExtents(disk);
+            if (unallocatedExtents == null)
+            {
+                return -1;
+            }
+
+            long result = 0;
+            for (int index = 0; index < unallocatedExtents.Count; index++)
+            {
+                DiskExtent extent = unallocatedExtents[index];
+                if (alignInSectors > 1)
+                {
+                    extent = DiskExtentHelper.GetAlignedDiskExtent(extent, alignInSectors);
+                }
+                if (extent.Size > result)
+                {
+                    result = extent.Size;
+                }
+            }
+            return result;
+        }
+
+        public static DiskExtent FindExtentAllocation(DynamicDisk disk, long allocationLength)
+        {
+            return FindExtentAllocation(disk, allocationLength, 0);
+        }
+
+        /// <param name="allocationLength">In bytes</param>
+        /// <param name="alignInSectors">0 or 1 for no alignment</param>
+        /// <returns>Allocated DiskExtent or null if there is not enough free disk space</returns>
+        public static DiskExtent FindExtentAllocation(DynamicDisk disk, long allocationLength, long alignInSectors)
+        {
+            List<DiskExtent> unallocatedExtents = DynamicDiskHelper.GetUnallocatedExtents(disk);
+            if (unallocatedExtents == null)
+            {
+                return null;
+            }
+
+            for (int index = 0; index < unallocatedExtents.Count; index++)
+            {
+                DiskExtent extent = unallocatedExtents[index];
+                if (alignInSectors > 1)
+                {
+                    extent = DiskExtentHelper.GetAlignedDiskExtent(extent, alignInSectors);
+                }
+                if (extent.Size >= allocationLength)
+                {
+                    return new DiskExtent(extent.Disk, extent.FirstSector, allocationLength);
+                }
+            }
+            return null;
+        }
+
+        /// <param name="targetOffset">in bytes</param>
+        public static bool IsMoveLocationValid(DynamicDisk disk, DynamicDiskExtent sourceExtent, long targetOffset)
+        {
+            List<DynamicDiskExtent> extents = GetDiskExtents(disk);
+            // extents are sorted by first sector
+            if (extents == null)
+            {
+                return false;
+            }
+
+            PrivateHeader privateHeader = disk.PrivateHeader;
+            if (targetOffset % privateHeader.BytesPerSector > 0)
+            {
+                return false;
+            }
+            long targetSector = targetOffset / disk.BytesPerSector;
+            DiskExtent targetExtent = new DiskExtent(disk.Disk, targetSector, sourceExtent.Size);
+
+            List<DiskExtent> usedExtents = new List<DiskExtent>();
+            foreach (DynamicDiskExtent usedExtent in usedExtents)
+            {
+                if (usedExtent.FirstSector != sourceExtent.FirstSector)
+                {
+                    usedExtents.Add(usedExtent);
+                }
+            }
+
+            long publicRegionStartSector = (long)privateHeader.PublicRegionStartLBA;
+            long publicRegionSize = (long)privateHeader.PublicRegionSizeLBA * disk.BytesPerSector;
+            List<DiskExtent> unallocatedExtents = DiskExtentsHelper.GetUnallocatedExtents(disk.Disk, publicRegionStartSector, publicRegionSize, usedExtents);
+            foreach (DiskExtent extent in unallocatedExtents)
+            {
+                if (extent.FirstSector <= targetExtent.FirstSector && targetExtent.LastSector <= extent.LastSector)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}

+ 1 - 72
DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicDiskHelper.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -28,76 +28,5 @@ namespace DiskAccessLibrary.LogicalDiskManager
             }
             return null;
         }
-
-        public static long FindUnusedRegionInPrivateRegion(DynamicDisk disk, int sectorCount)
-        {
-            bool[] bitmap = GetPrivateRegionUsageBitmap(disk);
-            
-            int startIndex = 0;
-            int freeCount = 0;
-            for (int index = 0; index < bitmap.Length; index++)
-            {
-                if (freeCount == 0)
-                {
-                    if (bitmap[index] == false) // free
-                    {
-                        startIndex = index;
-                        freeCount++;
-                    }
-                }
-                else
-                {
-                    if (bitmap[index] == false) // free
-                    {
-                        freeCount++;
-                        if (freeCount == sectorCount)
-                        {
-                            return (long)disk.PrivateHeader.PrivateRegionStartLBA + startIndex;
-                        }
-                    }
-                    else
-                    {
-                        freeCount = 0;
-                    }
-                }
-            }
-
-            return -1;
-        }
-
-        public static long FindUnusedSectorInPrivateRegion(DynamicDisk disk)
-        {
-            bool[] bitmap = GetPrivateRegionUsageBitmap(disk);
-            
-            for (int index = 0; index < bitmap.Length; index++)
-            {
-                if (bitmap[index] == false)
-                {
-                    return (long)disk.PrivateHeader.PrivateRegionStartLBA + index;
-                }
-            }
-            return -1;
-        }
-
-        public static bool[] GetPrivateRegionUsageBitmap(DynamicDisk disk)
-        {
-            // usage bitmap:
-            bool[] bitmap = new bool[disk.PrivateHeader.PrivateRegionSizeLBA];
-            bitmap[disk.PrivateHeader.PrimaryPrivateHeaderLBA] = true;
-            bitmap[disk.PrivateHeader.SecondaryPrivateHeaderLBA] = true;
-            bitmap[disk.PrivateHeader.PrimaryTocLBA] = true;
-            bitmap[disk.PrivateHeader.PreviousPrimaryTocLBA] = true;
-            bitmap[disk.PrivateHeader.SecondaryTocLBA] = true;
-            bitmap[disk.PrivateHeader.PreviousSecondaryTocLBA] = true;
-
-            foreach (TOCRegion region in disk.TOCBlock.Regions)
-            {
-                for (int index = 0; index < (int)region.SizeLBA; index++)
-                {
-                    bitmap[(int)region.StartLBA + index] = true;
-                }
-            }
-            return bitmap;
-        }
     }
 }

+ 6 - 4
DiskAccessLibrary/LogicalDiskManager/Helpers/DynamicVolumeHelper.cs

@@ -185,8 +185,9 @@ namespace DiskAccessLibrary.LogicalDiskManager
         private static Raid5Volume GetRAID5Volume(List<DynamicDisk> disks, VolumeManagerDatabase database, ComponentRecord componentRecord, VolumeRecord volumeRecord)
         {
             List<DynamicColumn> columns = GetDynamicVolumeColumns(disks, database, componentRecord, volumeRecord);
-
-            Raid5Volume volume = new Raid5Volume(columns, (int)componentRecord.StripeSizeLBA, volumeRecord.VolumeGuid, database.DiskGroupGuid);
+            int bytesPerSector = DynamicVolume.GetBytesPerSector(columns, DynamicColumn.DefaultBytesPerSector);
+            int sectorsPerStripe = (int)PublicRegionHelper.TranslateFromPublicRegionSizeLBA((int)componentRecord.StripeSizeLBA, bytesPerSector);
+            Raid5Volume volume = new Raid5Volume(columns, sectorsPerStripe, volumeRecord.VolumeGuid, database.DiskGroupGuid);
             volume.VolumeID = volumeRecord.VolumeId;
             volume.Name = volumeRecord.Name;
             volume.DiskGroupName = database.DiskGroupName;
@@ -196,8 +197,9 @@ namespace DiskAccessLibrary.LogicalDiskManager
         private static StripedVolume GetStripedVolume(List<DynamicDisk> disks, VolumeManagerDatabase database, ComponentRecord componentRecord, VolumeRecord volumeRecord)
         {
             List<DynamicColumn> columns = GetDynamicVolumeColumns(disks, database, componentRecord, volumeRecord);
-
-            StripedVolume volume = new StripedVolume(columns, (int)componentRecord.StripeSizeLBA, volumeRecord.VolumeGuid, database.DiskGroupGuid);
+            int bytesPerSector = DynamicVolume.GetBytesPerSector(columns, DynamicColumn.DefaultBytesPerSector);
+            int sectorsPerStripe = (int)PublicRegionHelper.TranslateFromPublicRegionSizeLBA((int)componentRecord.StripeSizeLBA, bytesPerSector);
+            StripedVolume volume = new StripedVolume(columns, sectorsPerStripe, volumeRecord.VolumeGuid, database.DiskGroupGuid);
             volume.VolumeID = volumeRecord.VolumeId;
             volume.Name = volumeRecord.Name;
             volume.DiskGroupName = database.DiskGroupName;

+ 103 - 0
DiskAccessLibrary/LogicalDiskManager/Helpers/PrivateRegionHelper.cs

@@ -0,0 +1,103 @@
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DiskAccessLibrary.LogicalDiskManager
+{
+    public class PrivateRegionHelper
+    {
+        public static long FindUnusedSector(PrivateHeader privateHeader, TOCBlock tocBlock)
+        {
+            return FindUnusedRegion(privateHeader, tocBlock, 1);
+        }
+
+        public static long FindUnusedRegion(PrivateHeader privateHeader, TOCBlock tocBlock, int sectorCount)
+        {
+            bool[] bitmap = GetPrivateRegionUsageBitmap(privateHeader, tocBlock);
+            // Reserve the first, second, third, third-last and second-last sectors for TOCBlocks
+            bitmap[0] = true;
+            bitmap[1] = true;
+            bitmap[2] = true;
+            bitmap[privateHeader.PrivateRegionSizeLBA - 3] = true;
+            bitmap[privateHeader.PrivateRegionSizeLBA - 2] = true;
+
+            int startIndex = 0;
+            int freeCount = 0;
+            for (int index = 0; index < bitmap.Length; index++)
+            {
+                if (bitmap[index] == false) // free
+                {
+                    if (freeCount == 0)
+                    {
+                        startIndex = index;
+                    }
+                    freeCount++;
+                    if (freeCount == sectorCount)
+                    {
+                        return (long)privateHeader.PrivateRegionStartLBA + startIndex;
+                    }
+                }
+                else
+                {
+                    freeCount = 0;
+                }
+            }
+
+            return -1;
+        }
+
+        // On disks with 512-byte sectors Windows will reserve sector 0 and alternate between sectors 1 and 2 of the private region.
+        // On disks with 4KB sectors Windows will alternate between sectors 0 and 1.
+        public static long FindUnusedLBAForPrimaryToc(PrivateHeader privateHeader, TOCBlock tocBlock)
+        {
+            bool[] bitmap = GetPrivateRegionUsageBitmap(privateHeader, tocBlock);
+            for (int index = 0; index < bitmap.Length; index++)
+            {
+                if (bitmap[index] == false)
+                {
+                    return (long)privateHeader.PrivateRegionStartLBA + index;
+                }
+            }
+            return -1;
+        }
+
+        // The secondary TOC is usually alternated between the third-last and the second-last sectors of the private region.
+        public static long FindUnusedLBAForSecondaryToc(PrivateHeader privateHeader, TOCBlock tocBlock)
+        {
+            bool[] bitmap = GetPrivateRegionUsageBitmap(privateHeader, tocBlock);
+            for (int index = bitmap.Length - 1; index >= 0; index--)
+            {
+                if (bitmap[index] == false)
+                {
+                    return (long)privateHeader.PrivateRegionStartLBA + index;
+                }
+            }
+            return -1;
+        }
+
+        private static bool[] GetPrivateRegionUsageBitmap(PrivateHeader privateHeader, TOCBlock tocBlock)
+        {
+            // usage bitmap:
+            bool[] bitmap = new bool[privateHeader.PrivateRegionSizeLBA];
+            bitmap[privateHeader.PrimaryPrivateHeaderLBA] = true;
+            bitmap[privateHeader.SecondaryPrivateHeaderLBA] = true;
+            bitmap[privateHeader.PrimaryTocLBA] = true;
+            bitmap[privateHeader.SecondaryTocLBA] = true;
+
+            foreach (TOCRegion region in tocBlock.Regions)
+            {
+                for (int index = 0; index < (int)region.SizeLBA; index++)
+                {
+                    bitmap[(int)region.StartLBA + index] = true;
+                }
+            }
+            return bitmap;
+        }
+    }
+}

+ 51 - 0
DiskAccessLibrary/LogicalDiskManager/Helpers/PublicRegionHelper.cs

@@ -0,0 +1,51 @@
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace DiskAccessLibrary.LogicalDiskManager
+{
+    public class PublicRegionHelper
+    {
+        /// <summary>
+        /// LBA values in extent / volume / component records refer to logical 512-byte blocks within the public region of the disk, regardless of the actual block size of the disk.
+        /// We have to translate those values in order to support disks with 4K sectors. 
+        /// </summary>
+        public const int BytesPerPublicRegionSector = 512;
+
+        public static long TranslateFromPublicRegionLBA(long publicRegionOffsetLBA, long publicRegionStartLBA, int bytesPerDiskSector)
+        {
+            return publicRegionStartLBA + publicRegionOffsetLBA * BytesPerPublicRegionSector / bytesPerDiskSector;
+        }
+
+        public static long TranslateFromPublicRegionSizeLBA(long sectorCount, int bytesPerDiskSector)
+        {
+            return sectorCount * BytesPerPublicRegionSector / bytesPerDiskSector;
+        }
+
+        public static long TranslateToPublicRegionLBA(long sectorIndex, PrivateHeader privateHeader)
+        {
+            return TranslateToPublicRegionLBA(sectorIndex, (long)privateHeader.PublicRegionStartLBA, (int)privateHeader.BytesPerSector);
+        }
+
+        public static long TranslateToPublicRegionLBA(long sectorIndex, long publicRegionStartLBA, int bytesPerDiskSector)
+        {
+            return (sectorIndex - publicRegionStartLBA) * bytesPerDiskSector / BytesPerPublicRegionSector;
+        }
+
+        public static long TranslateToPublicRegionSizeLBA(long sectorCount, PrivateHeader privateHeader)
+        {
+            return TranslateToPublicRegionSizeLBA(sectorCount, (int)privateHeader.BytesPerSector);
+        }
+
+        public static long TranslateToPublicRegionSizeLBA(long sectorCount, int bytesPerDiskSector)
+        {
+            return sectorCount * bytesPerDiskSector / BytesPerPublicRegionSector;
+        }
+    }
+}

+ 23 - 23
DiskAccessLibrary/LogicalDiskManager/Helpers/VolumeManagerDatabaseHelper.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -83,7 +83,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
                 throw new MissingDatabaseRecordException("Volume record is missing");
             }
             volumeRecord = (VolumeRecord)volumeRecord.Clone();
-            volumeRecord.SizeLBA += (ulong)newExtent.TotalSectors;
+            volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(newExtent.TotalSectors, privateHeader);
             records.Add(volumeRecord);
 
             ComponentRecord componentRecord = database.FindComponentsByVolumeID(volumeRecord.VolumeId)[0];
@@ -108,8 +108,8 @@ namespace DiskAccessLibrary.LogicalDiskManager
             newExtentRecord.Name = GetNextExtentName(database.ExtentRecords, diskRecord.Name);
             newExtentRecord.ComponentId = componentRecord.ComponentId;
             newExtentRecord.DiskId = diskRecord.DiskId;
-            newExtentRecord.DiskOffsetLBA = (ulong)newExtent.FirstSector - privateHeader.PublicRegionStartLBA;
-            newExtentRecord.SizeLBA = (ulong)newExtent.TotalSectors;
+            newExtentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(newExtent.FirstSector, privateHeader);
+            newExtentRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(newExtent.TotalSectors, privateHeader);
             newExtentRecord.HasColumnIndexFlag = true;
             newExtentRecord.ColumnIndex = (uint)volume.Columns.Count; // zero based
                         
@@ -158,7 +158,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
             ExtentRecord sourceExtentRecord = database.FindExtentByExtentID(relocatedExtent.ExtentID);
             ExtentRecord relocatedExtentRecord = (ExtentRecord)sourceExtentRecord.Clone();
             relocatedExtentRecord.DiskId = targetDiskRecord.DiskId;
-            relocatedExtentRecord.DiskOffsetLBA = (ulong)relocatedExtent.FirstSector - privateHeader.PublicRegionStartLBA;
+            relocatedExtentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(relocatedExtent.FirstSector, privateHeader);
             records.Add(relocatedExtentRecord);
 
             // we should update the disk records
@@ -182,14 +182,14 @@ namespace DiskAccessLibrary.LogicalDiskManager
             database.UpdateDatabase(records);
         }
 
-        public static void ExtendSimpleVolume(DiskGroupDatabase database, SimpleVolume volume, long additionalNumberOfSectors)
+        public static void ExtendSimpleVolume(DiskGroupDatabase database, SimpleVolume volume, long numberOfAdditionalSectors)
         {
             VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid);
             volumeRecord = (VolumeRecord)volumeRecord.Clone();
-            volumeRecord.SizeLBA += (ulong)additionalNumberOfSectors;
+            volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalSectors, volume.BytesPerSector);
             ExtentRecord extentRecord = database.FindExtentByExtentID(volume.DiskExtent.ExtentID);
             extentRecord = (ExtentRecord)extentRecord.Clone();
-            extentRecord.SizeLBA += (ulong)additionalNumberOfSectors;
+            extentRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalSectors, volume.BytesPerSector);
             DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs
             diskRecord = (DiskRecord)diskRecord.Clone();
 
@@ -201,9 +201,9 @@ namespace DiskAccessLibrary.LogicalDiskManager
             database.UpdateDatabase(records);
         }
 
-        public static void ExtendStripedVolume(DiskGroupDatabase database, StripedVolume volume, long additionalNumberOfExtentSectors)
+        public static void ExtendStripedVolume(DiskGroupDatabase database, StripedVolume volume, long numberOfAdditionalExtentSectors)
         {
-            if (additionalNumberOfExtentSectors % volume.SectorsPerStripe > 0)
+            if (numberOfAdditionalExtentSectors % volume.SectorsPerStripe > 0)
             {
                 throw new ArgumentException("Number of additional sectors must be multiple of stripes per sector");
             }
@@ -212,7 +212,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
 
             VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid);
             volumeRecord = (VolumeRecord)volumeRecord.Clone();
-            volumeRecord.SizeLBA += (ulong)(additionalNumberOfExtentSectors * volume.NumberOfColumns);
+            volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors * volume.NumberOfColumns, volume.BytesPerSector);
             records.Add(volumeRecord);
 
             // we only want to extend the last extent in each column
@@ -221,7 +221,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
                 DynamicDiskExtent lastExtent = column.Extents[column.Extents.Count - 1];
                 ExtentRecord extentRecord = database.FindExtentByExtentID(lastExtent.ExtentID);
                 extentRecord = (ExtentRecord)extentRecord.Clone();
-                extentRecord.SizeLBA += (ulong)additionalNumberOfExtentSectors;
+                extentRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors, volume.BytesPerSector);
                 records.Add(extentRecord);
 
                 DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs
@@ -232,9 +232,9 @@ namespace DiskAccessLibrary.LogicalDiskManager
             database.UpdateDatabase(records);
         }
 
-        public static void ExtendRAID5Volume(DiskGroupDatabase database, Raid5Volume volume, long additionalNumberOfExtentSectors)
+        public static void ExtendRAID5Volume(DiskGroupDatabase database, Raid5Volume volume, long numberOfAdditionalExtentSectors)
         {
-            if (additionalNumberOfExtentSectors % volume.SectorsPerStripe > 0)
+            if (numberOfAdditionalExtentSectors % volume.SectorsPerStripe > 0)
             {
                 throw new ArgumentException("Number of additional sectors must be multiple of stripes per sector");
             }
@@ -243,7 +243,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
 
             VolumeRecord volumeRecord = database.FindVolumeByVolumeGuid(volume.VolumeGuid);
             volumeRecord = (VolumeRecord)volumeRecord.Clone();
-            volumeRecord.SizeLBA += (ulong)(additionalNumberOfExtentSectors * (volume.NumberOfColumns - 1));
+            volumeRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors * (volume.NumberOfColumns - 1), volume.BytesPerSector);
             records.Add(volumeRecord);
 
             foreach (DynamicColumn column in volume.Columns)
@@ -251,7 +251,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
                 DynamicDiskExtent lastExtent = column.Extents[column.Extents.Count - 1];
                 ExtentRecord extentRecord = database.FindExtentByExtentID(lastExtent.ExtentID);
                 extentRecord = (ExtentRecord)extentRecord.Clone();
-                extentRecord.SizeLBA += (ulong)additionalNumberOfExtentSectors;
+                extentRecord.SizeLBA += (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(numberOfAdditionalExtentSectors, volume.BytesPerSector);
                 records.Add(extentRecord);
 
                 DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); // we should update the disk, see Database.cs
@@ -275,7 +275,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
             volumeRecord.VolumeNumber = GetNextVolumeNumber(database.VolumeRecords);
             volumeRecord.VolumeFlags = VolumeFlags.Writeback | VolumeFlags.DefaultUnknown;
             volumeRecord.NumberOfComponents = 1;
-            volumeRecord.SizeLBA = (ulong)(extent.TotalSectors);
+            volumeRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extent.TotalSectors, extent.BytesPerSector);
             volumeRecord.PartitionType = PartitionType.RAW;
             volumeRecord.VolumeGuid = Guid.NewGuid();
             records.Add(volumeRecord);
@@ -299,8 +299,8 @@ namespace DiskAccessLibrary.LogicalDiskManager
 
             ExtentRecord extentRecord = new ExtentRecord();
             extentRecord.Name = GetNextExtentName(database.ExtentRecords, diskRecord.Name);
-            extentRecord.DiskOffsetLBA = (ulong)extent.FirstSector - privateHeader.PublicRegionStartLBA;
-            extentRecord.SizeLBA = (ulong)extent.TotalSectors;
+            extentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(extent.FirstSector, privateHeader);
+            extentRecord.SizeLBA = volumeRecord.SizeLBA;
             extentRecord.ComponentId = componentRecord.ComponentId;
             extentRecord.DiskId = diskRecord.DiskId;
 
@@ -336,7 +336,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
             volumeRecord.VolumeNumber = GetNextVolumeNumber(database.VolumeRecords);
             volumeRecord.VolumeFlags = VolumeFlags.Writeback | VolumeFlags.Writecopy | VolumeFlags.DefaultUnknown;
             volumeRecord.NumberOfComponents = 1;
-            volumeRecord.SizeLBA = (ulong)(extents[0].TotalSectors * (numberOfColumns - 1));
+            volumeRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extents[0].TotalSectors * (numberOfColumns - 1), extents[0].BytesPerSector);
             volumeRecord.PartitionType = PartitionType.RAW;
             volumeRecord.VolumeGuid = Guid.NewGuid();
             records.Add(volumeRecord);
@@ -365,8 +365,8 @@ namespace DiskAccessLibrary.LogicalDiskManager
 
                 ExtentRecord extentRecord = new ExtentRecord();
                 extentRecord.Name = GetNextExtentName(database.ExtentRecords, diskRecord.Name);
-                extentRecord.DiskOffsetLBA = (ulong)extent.FirstSector - privateHeader.PublicRegionStartLBA;
-                extentRecord.SizeLBA = (ulong)extent.TotalSectors;
+                extentRecord.DiskOffsetLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionLBA(extent.FirstSector, privateHeader);
+                extentRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extent.TotalSectors, extent.BytesPerSector);
                 extentRecord.ComponentId = componentRecord.ComponentId;
                 extentRecord.DiskId = diskRecord.DiskId;
                 
@@ -390,7 +390,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
                 ExtentRecord extentRecord = new ExtentRecord();
                 extentRecord.Name = diskRecord.Name + "-01";
                 extentRecord.ExtentFlags = ExtentFlags.Recover;
-                extentRecord.SizeLBA = (ulong)extents[0].TotalSectors;
+                extentRecord.SizeLBA = (ulong)PublicRegionHelper.TranslateToPublicRegionSizeLBA(extents[0].TotalSectors, extents[0].BytesPerSector);
                 extentRecord.ComponentId = componentRecord.ComponentId;
                 extentRecord.DiskId = diskRecord.DiskId;
                 extentRecord.HasColumnIndexFlag = true;

+ 27 - 8
DiskAccessLibrary/LogicalDiskManager/KernelUpdateLog/KernelUpdateLogPage.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -19,7 +19,11 @@ namespace DiskAccessLibrary.LogicalDiskManager
         public uint RecoverySequenceNumber; // DMDiag calls this: recover_seqno
     }
 
-    public class KernelUpdateLogPage // KLOG sector
+    /// <summary>
+    /// Each KLOG page is 512 bytes.
+    /// Note: a sector can contain more than one KLOG page.
+    /// </summary>
+    public class KernelUpdateLogPage
     {
         public const int Length = 512;
 
@@ -116,19 +120,34 @@ namespace DiskAccessLibrary.LogicalDiskManager
             m_logEntries.Add(entry);
         }
 
-        public static KernelUpdateLogPage ReadFromDisk(Disk disk, PrivateHeader privateHeader, TOCBlock tocBlock, int kLogIndex)
+        public static KernelUpdateLogPage ReadFromDisk(Disk disk, PrivateHeader privateHeader, TOCBlock tocBlock, int pageIndex)
         {
-            ulong sectorIndex = privateHeader.PrivateRegionStartLBA + tocBlock.LogStart + (uint)kLogIndex;
+            ulong sectorIndex = privateHeader.PrivateRegionStartLBA + tocBlock.LogStart + (uint)(pageIndex * Length / disk.BytesPerSector);
+            int pageOffset = (pageIndex * Length) % disk.BytesPerSector;
             byte[] sector = disk.ReadSector((long)sectorIndex);
+            if (pageOffset > 0)
+            {
+                sector = ByteReader.ReadBytes(sector, pageOffset, Length);
+            }
             KernelUpdateLogPage result = new KernelUpdateLogPage(sector);
             return result;
         }
 
-        public static void WriteToDisk(Disk disk, PrivateHeader privateHeader, TOCBlock tocBlock, KernelUpdateLogPage record)
+        public static void WriteToDisk(Disk disk, PrivateHeader privateHeader, TOCBlock tocBlock, KernelUpdateLogPage page)
         {
-            ulong sectorIndex = privateHeader.PrivateRegionStartLBA + tocBlock.LogStart + record.PageIndex;
-            byte[] sector = record.GetBytes();
-            disk.WriteSectors((long)sectorIndex, sector);
+            ulong sectorIndex = privateHeader.PrivateRegionStartLBA + tocBlock.LogStart + (uint)(page.PageIndex * Length / disk.BytesPerSector);
+            int pageOffset = ((int)page.PageIndex * Length) % disk.BytesPerSector;
+            byte[] pageBytes = page.GetBytes();
+            if (disk.BytesPerSector > Length)
+            {
+                byte[] sectorBytes = disk.ReadSector((long)sectorIndex);
+                ByteWriter.WriteBytes(sectorBytes, pageOffset, pageBytes);
+                disk.WriteSectors((long)sectorIndex, sectorBytes);
+            }
+            else
+            {
+                disk.WriteSectors((long)sectorIndex, pageBytes);
+            }
         }
     }
 }

+ 14 - 36
DiskAccessLibrary/LogicalDiskManager/PrivateHeader.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -70,6 +70,11 @@ namespace DiskAccessLibrary.LogicalDiskManager
 
         public PrivateHeader(byte[] buffer)
         {
+            if (buffer.Length > Length)
+            {
+                // Checksum only applies to the first 512 bytes (even when the sector size > 512 bytes)
+                buffer = ByteReader.ReadBytes(buffer, 0, 512);
+            }
             Signature = ByteReader.ReadAnsiString(buffer, 0x00, 8);
             uint checksum = BigEndianConverter.ToUInt32(buffer, 0x08);
             MajorVersion = BigEndianConverter.ToUInt16(buffer, 0x0C);
@@ -106,6 +111,9 @@ namespace DiskAccessLibrary.LogicalDiskManager
             m_isChecksumValid = (checksum == CalculateChecksum(buffer));
         }
 
+        /// <summary>
+        /// Private header may need to be padded with zeros in order to fill an entire sector
+        /// </summary>
         public byte[] GetBytes()
         {
             byte[] buffer = new byte[Length];
@@ -236,8 +244,12 @@ namespace DiskAccessLibrary.LogicalDiskManager
         }
 
         public static void WriteToDisk(Disk disk, PrivateHeader privateHeader)
-        { 
+        {
             byte[] bytes = privateHeader.GetBytes();
+            if (disk.BytesPerSector > Length)
+            {
+                bytes = ByteUtils.Concatenate(bytes, new byte[disk.BytesPerSector - PrivateHeader.Length]);
+            }
 
             disk.WriteSectors((long)(privateHeader.PrivateRegionStartLBA +  privateHeader.PrimaryPrivateHeaderLBA), bytes);
             disk.WriteSectors((long)(privateHeader.PrivateRegionStartLBA + privateHeader.SecondaryPrivateHeaderLBA), bytes);
@@ -275,40 +287,6 @@ namespace DiskAccessLibrary.LogicalDiskManager
             }
         }
 
-        
-        // I'm not aware of any parameter that will tell us where the previous TOCs are,
-        // I'm assuming it is given that there will be TOCs (in the private region) at Sectors 1,2, PrivateRegionSizeLBA - 2 and PrivateRegionSizeLBA - 1,
-        // And so PrimaryTocLBA, SecondaryTocLBA simply point to the ones being used
-        public ulong PreviousPrimaryTocLBA
-        {
-            get
-            {
-                if (PrimaryTocLBA == 1)
-                {
-                    return PrimaryTocLBA + 1;
-                }
-                else
-                {
-                    return PrimaryTocLBA - 1;
-                }
-            }
-        }
-
-        public ulong PreviousSecondaryTocLBA
-        {
-            get
-            {
-                if (PrimaryTocLBA == 1)
-                {
-                    return SecondaryTocLBA - 1;
-                }
-                else
-                {
-                    return SecondaryTocLBA + 1;
-                }
-            }
-        }
-
         public bool IsChecksumValid
         {
             get

+ 20 - 6
DiskAccessLibrary/LogicalDiskManager/TOCBlock/TOCBlock.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -27,6 +27,11 @@ namespace DiskAccessLibrary.LogicalDiskManager
 
         public TOCBlock(byte[] buffer)
         {
+            if (buffer.Length > Length)
+            {
+                // Checksum only applies to the first 512 bytes (even when the sector size > 512 bytes)
+                buffer = ByteReader.ReadBytes(buffer, 0, 512);
+            }
             Signature = ByteReader.ReadAnsiString(buffer, 0x00, 8);
             uint checksum = BigEndianConverter.ToUInt32(buffer, 0x08);
             UpdateSequenceNumber = BigEndianConverter.ToUInt64(buffer, 0x0C);
@@ -45,6 +50,9 @@ namespace DiskAccessLibrary.LogicalDiskManager
             m_isChecksumValid = (checksum == PrivateHeader.CalculateChecksum(buffer));
         }
 
+        /// <summary>
+        /// TOC Block may need to be padded with zeros in order to fill an entire sector
+        /// </summary>
         public byte[] GetBytes()
         {
             byte[] buffer = new byte[Length];
@@ -106,11 +114,17 @@ namespace DiskAccessLibrary.LogicalDiskManager
         {
             privateHeader.UpdateSequenceNumber++;
             tocBlock.UpdateSequenceNumber++;
-            byte[] bytes = tocBlock.GetBytes();
-            disk.WriteSectors((long)(privateHeader.PrivateRegionStartLBA + privateHeader.PreviousPrimaryTocLBA), bytes);
-            disk.WriteSectors((long)(privateHeader.PrivateRegionStartLBA + privateHeader.PreviousSecondaryTocLBA), bytes);
-            privateHeader.PrimaryTocLBA = privateHeader.PreviousPrimaryTocLBA;
-            privateHeader.SecondaryTocLBA = privateHeader.PreviousSecondaryTocLBA;
+            byte[] tocBytes = tocBlock.GetBytes();
+            if (disk.BytesPerSector > Length)
+            {
+                tocBytes = ByteUtils.Concatenate(tocBytes, new byte[disk.BytesPerSector - TOCBlock.Length]);
+            }
+            long alternatePrimaryTOCLBA = PrivateRegionHelper.FindUnusedLBAForPrimaryToc(privateHeader, tocBlock);
+            long alternateSecondaryTOCLBA = PrivateRegionHelper.FindUnusedLBAForSecondaryToc(privateHeader, tocBlock);
+            disk.WriteSectors(alternatePrimaryTOCLBA, tocBytes);
+            disk.WriteSectors(alternateSecondaryTOCLBA, tocBytes);
+            privateHeader.PrimaryTocLBA = (ulong)alternatePrimaryTOCLBA;
+            privateHeader.SecondaryTocLBA = (ulong)alternateSecondaryTOCLBA;
             PrivateHeader.WriteToDisk(disk, privateHeader);
         }
 

+ 10 - 7
DiskAccessLibrary/LogicalDiskManager/VolumeManagerDatabase.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -590,9 +590,11 @@ namespace DiskAccessLibrary.LogicalDiskManager
             }
             List<DatabaseRecord> databaseRecords = new List<DatabaseRecord>();
 
-            // The first VBLK entry is the subsequent entry to the VMDB, which located at (ConfigurationStartLBA + Item1Start)
-            ulong firstSector = privateHeader.PrivateRegionStartLBA + tocBlock.ConfigStart + 1;  // we skip the VMDB
-            int sectorCount = (int)Math.Ceiling(databaseHeader.NumberOfVBlks * databaseHeader.BlockSize / (double)disk.BytesPerSector);
+            // The first VBLK entry is the subsequent entry to the VMDB header.
+            // Note: On a disk with 4KB sectors, VBLKs will reside in the same sector as the VMDB header.
+            ulong firstSector = privateHeader.PrivateRegionStartLBA + tocBlock.ConfigStart;  // we skip the VMDB
+            int databaseLength = (int)(databaseHeader.HeaderSize + databaseHeader.NumberOfVBlks * databaseHeader.BlockSize);
+            int sectorCount = (int)Math.Ceiling(databaseLength / (double)disk.BytesPerSector);
             byte[] databaseBytes = disk.ReadSectors((long)firstSector, sectorCount);
 
             // read all VBLK blocks:
@@ -601,7 +603,8 @@ namespace DiskAccessLibrary.LogicalDiskManager
             for (uint index = 0; index < databaseHeader.NumberOfVBlks - 4; index++)
             {
                 byte[] fragmentBytes = new byte[databaseHeader.BlockSize];
-                Array.Copy(databaseBytes, (long)index * databaseHeader.BlockSize, fragmentBytes, 0, databaseHeader.BlockSize);
+                int fragmentOffset = (int)(databaseHeader.HeaderSize + index * databaseHeader.BlockSize);
+                Array.Copy(databaseBytes, fragmentOffset, fragmentBytes, 0, databaseHeader.BlockSize);
                 DatabaseRecordFragment fragment = DatabaseRecordFragment.GetDatabaseRecordFragment(fragmentBytes);
 
                 if (fragment != null) // null fragment means VBLK is empty
@@ -622,9 +625,9 @@ namespace DiskAccessLibrary.LogicalDiskManager
             // We have all the fragments and we can now assemble the records:
             // We assume that fragments with lower FragmentNumber appear in the database before fragments
             // of the same group with higher FragmentNumber.
-            foreach (List<DatabaseRecordFragment> recorFragments in fragments.Values)
+            foreach (List<DatabaseRecordFragment> recordFragments in fragments.Values)
             {
-                DatabaseRecord databaseRecord = DatabaseRecord.GetDatabaseRecord(recorFragments);
+                DatabaseRecord databaseRecord = DatabaseRecord.GetDatabaseRecord(recordFragments);
                 databaseRecords.Add(databaseRecord);
             }
 

+ 14 - 2
DiskAccessLibrary/LogicalDiskManager/VolumeManagerDatabaseHeader.cs

@@ -74,6 +74,9 @@ namespace DiskAccessLibrary.LogicalDiskManager
             LastModificationDT = DateTime.FromFileTimeUtc(BigEndianConverter.ToInt64(buffer, 0xBD));
         }
 
+        /// <summary>
+        /// VBLKs may reside in the same sector as the VMDB header.
+        /// </summary>
         public byte[] GetBytes()
         {
             byte[] buffer = new byte[Length];
@@ -142,8 +145,17 @@ namespace DiskAccessLibrary.LogicalDiskManager
         public static void WriteToDisk(Disk disk, PrivateHeader privateHeader, TOCBlock tocBlock, VolumeManagerDatabaseHeader header)
         {
             ulong sectorIndex = privateHeader.PrivateRegionStartLBA + tocBlock.ConfigStart;
-            byte[] bytes = header.GetBytes();
-            disk.WriteSectors((long)sectorIndex, bytes);
+            byte[] headerBytes = header.GetBytes();
+            if (disk.BytesPerSector > Length)
+            {
+                byte[] sectorBytes = disk.ReadSector((long)sectorIndex);
+                ByteWriter.WriteBytes(sectorBytes, 0, headerBytes);
+                disk.WriteSectors((long)sectorIndex, sectorBytes);
+            }
+            else
+            {
+                disk.WriteSectors((long)sectorIndex, headerBytes);
+            }
         }
     }
 }

+ 41 - 4
DiskAccessLibrary/LogicalDiskManager/Volumes/DynamicColumn.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -19,16 +19,21 @@ namespace DiskAccessLibrary.LogicalDiskManager
     /// </summary>
     public class DynamicColumn
     {
+        public const int DefaultBytesPerSector = 512; // for missing disks
+
+        private int m_bytesPerSector;
         List<DynamicDiskExtent> m_extents = new List<DynamicDiskExtent>();
 
         public DynamicColumn(DynamicDiskExtent extent)
         {
             m_extents.Add(extent);
+            m_bytesPerSector = GetBytesPerSector(m_extents, DefaultBytesPerSector);
         }
 
         public DynamicColumn(List<DynamicDiskExtent> extents)
         {
             m_extents = extents;
+            m_bytesPerSector = GetBytesPerSector(m_extents, DefaultBytesPerSector);
         }
 
         private List<ArrayPosition> TranslateSectors(long startSectorIndex, int sectorCount)
@@ -67,7 +72,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
         {
             List<ArrayPosition> readPositions = TranslateSectors(sectorIndex, sectorCount);
 
-            byte[] result = new byte[sectorCount * DynamicVolume.BytesPerDynamicDiskSector];
+            byte[] result = new byte[sectorCount * BytesPerSector];
             int bytesRead = 0;
             foreach (ArrayPosition readPosition in readPositions)
             {
@@ -83,14 +88,14 @@ namespace DiskAccessLibrary.LogicalDiskManager
 
         public void WriteSectors(long sectorIndex, byte[] data)
         {
-            int sectorCount = data.Length / DynamicVolume.BytesPerDynamicDiskSector;
+            int sectorCount = data.Length / BytesPerSector;
             List<ArrayPosition> writePositions = TranslateSectors(sectorIndex, sectorCount);
 
             int bytesWritten = 0;
             foreach (ArrayPosition writePosition in writePositions)
             {
                 DynamicDiskExtent extent = m_extents[writePosition.DiskIndex];
-                byte[] extentBytes = new byte[writePosition.SectorCount * DynamicVolume.BytesPerDynamicDiskSector];
+                byte[] extentBytes = new byte[writePosition.SectorCount * BytesPerSector];
                 Array.Copy(data, bytesWritten, extentBytes, 0, extentBytes.Length);
                 extent.WriteSectors(writePosition.SectorIndex, extentBytes);
                 
@@ -119,6 +124,17 @@ namespace DiskAccessLibrary.LogicalDiskManager
             }
         }
 
+        /// <summary>
+        /// "All disks holding extents for a given volume must have the same sector size"
+        /// </summary>
+        public int BytesPerSector
+        {
+            get
+            {
+                return m_bytesPerSector;
+            }
+        }
+
         public bool IsOperational
         {
             get
@@ -133,5 +149,26 @@ namespace DiskAccessLibrary.LogicalDiskManager
                 return true;
             }
         }
+
+        public static int GetBytesPerSector(List<DynamicDiskExtent> extents, int defaultValue)
+        {
+            int? bytesPerSector = GetBytesPerSector(extents);
+            return bytesPerSector.HasValue ? bytesPerSector.Value : defaultValue;
+        }
+
+        /// <summary>
+        /// "All disks holding extents for a given volume must have the same sector size"
+        /// </summary>
+        public static int? GetBytesPerSector(List<DynamicDiskExtent> extents)
+        {
+            foreach (DynamicDiskExtent extent in extents)
+            {
+                if (extent.Disk != null)
+                {
+                    return extent.BytesPerSector;
+                }
+            }
+            return null;
+        }
     }
 }

+ 32 - 7
DiskAccessLibrary/LogicalDiskManager/Volumes/DynamicVolume.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -15,16 +15,12 @@ namespace DiskAccessLibrary.LogicalDiskManager
 {
     public abstract class DynamicVolume : Volume
     {
-        // dynamic disks will only work with 512-byte sectors
-        // http://msdn.microsoft.com/en-us/windows/hardware/gg463528
-        // Note: Simple volumes will somewhat work on > 512-byte sectors
-        public const int BytesPerDynamicDiskSector = 512;
-
         private Guid m_volumeGuid;
         private Guid m_diskGroupGuid;
         public ulong VolumeID;
         public string Name;
         public string DiskGroupName;
+        private int? m_bytesPerSector;
 
         public DynamicVolume(Guid volumeGuid, Guid diskGroupGuid)
         {
@@ -46,11 +42,18 @@ namespace DiskAccessLibrary.LogicalDiskManager
             return this.VolumeGuid.GetHashCode();
         }
 
+        /// <summary>
+        /// "All disks holding extents for a given volume must have the same sector size"
+        /// </summary>
         public override int BytesPerSector
         {
             get
             {
-                return BytesPerDynamicDiskSector;
+                if (!m_bytesPerSector.HasValue)
+                {
+                    m_bytesPerSector = GetBytesPerSector(this.Columns, DynamicColumn.DefaultBytesPerSector);
+                }
+                return m_bytesPerSector.Value;
             }
         }
 
@@ -123,5 +126,27 @@ namespace DiskAccessLibrary.LogicalDiskManager
                 return IsHealthy;
             }
         }
+
+        public static int GetBytesPerSector(List<DynamicColumn> columns, int defaultValue)
+        {
+            int? bytesPerSector = GetBytesPerSector(columns);
+            return bytesPerSector.HasValue ? bytesPerSector.Value : defaultValue;
+        }
+
+        /// <summary>
+        /// "All disks holding extents for a given volume must have the same sector size"
+        /// </summary>
+        public static int? GetBytesPerSector(List<DynamicColumn> columns)
+        {
+            foreach (DynamicColumn column in columns)
+            {
+                int? bytesPerSector = DynamicColumn.GetBytesPerSector(column.Extents);
+                if (bytesPerSector.HasValue)
+                {
+                    return bytesPerSector.Value;
+                }
+            }
+            return null;
+        }
     }
 }

+ 2 - 10
DiskAccessLibrary/LogicalDiskManager/Volumes/Raid5Volume.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -92,7 +92,7 @@ namespace DiskAccessLibrary.LogicalDiskManager
                 }
                 else
                 {
-                    stripeBytes = new byte[readPosition.SectorCount * BytesPerDynamicDiskSector];
+                    stripeBytes = new byte[readPosition.SectorCount * BytesPerSector];
                     for (int index = 0; index < m_columns.Count; index++)
                     {
                         if (index != readPosition.DiskIndex)
@@ -198,14 +198,6 @@ namespace DiskAccessLibrary.LogicalDiskManager
             WriteSectors(stripeIndex * m_sectorsPerStripe, data);
         }
 
-        public override int BytesPerSector
-        {
-            get 
-            {
-                return BytesPerDynamicDiskSector;
-            }
-        }
-
         public override long Size
         {
             get 

DiskAccessLibrary/Disks/GuidPartitionTable/GuidPartitionEntry.cs → DiskAccessLibrary/PartitionTables/GuidPartitionTable/GuidPartitionEntry.cs


DiskAccessLibrary/Disks/GuidPartitionTable/GuidPartitionEntryCollection.cs → DiskAccessLibrary/PartitionTables/GuidPartitionTable/GuidPartitionEntryCollection.cs


DiskAccessLibrary/Disks/GuidPartitionTable/GuidPartitionTable.cs → DiskAccessLibrary/PartitionTables/GuidPartitionTable/GuidPartitionTable.cs


DiskAccessLibrary/Disks/GuidPartitionTable/GuidPartitionTableHeader.cs → DiskAccessLibrary/PartitionTables/GuidPartitionTable/GuidPartitionTableHeader.cs


DiskAccessLibrary/Disks/MasterBootRecord/CHSAddress.cs → DiskAccessLibrary/PartitionTables/MasterBootRecord/CHSAddress.cs


DiskAccessLibrary/Disks/MasterBootRecord/MasterBootRecord.cs → DiskAccessLibrary/PartitionTables/MasterBootRecord/MasterBootRecord.cs


DiskAccessLibrary/Disks/MasterBootRecord/PartitionTableEntry.cs → DiskAccessLibrary/PartitionTables/MasterBootRecord/PartitionTableEntry.cs


DiskAccessLibrary/Disks/MasterBootRecord/PartitionTypeName.cs → DiskAccessLibrary/PartitionTables/MasterBootRecord/PartitionTypeName.cs


+ 2 - 2
DiskAccessLibrary/Properties/AssemblyInfo.cs

@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
 //
 // You can specify all the values or you can default the Revision and Build Numbers 
 // by using the '*' as shown below:
-[assembly: AssemblyVersion("1.4.1.3")]
-[assembly: AssemblyFileVersion("1.4.1.3")]
+[assembly: AssemblyVersion("1.4.2.0")]
+[assembly: AssemblyFileVersion("1.4.2.0")]

+ 8 - 0
DiskAccessLibrary/RevisionHistory.txt

@@ -76,3 +76,11 @@ Revision History:
 1.4.0 - API improvements.
 
 1.4.1 - Improved volume locking mechanism.
+
+1.4.2 - Support for disks with 4KB sectors.
+        Minor improvements and bugfixes to DiskImage and inheriting classes.
+        Bugfix: RawDiskImage.Extend had issue with closing the filestream.
+        Bugfix: allocation within the private region of a dynamic disk did not function properly.
+        API: Added RAM-Disk implementation.
+        API: Added RawDiskImage.Create method.
+        API: Added BasicDiskHelper.GetUnallocatedExtents method.

+ 9 - 1
DiskAccessLibrary/Volumes/Partition.cs

@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+/* Copyright (C) 2014-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  * 
  * You can redistribute this program and/or modify it under the terms of
  * the GNU Lesser Public License as published by the Free Software Foundation,
@@ -51,6 +51,14 @@ namespace DiskAccessLibrary
             }
         }
 
+        public DiskExtent Extent
+        {
+            get
+            {
+                return m_extent;
+            }
+        }
+
         public override List<DiskExtent> Extents
         {
             get

+ 0 - 18
DiskAccessLibrary/Win32/Disks/DiskImage.Win32.cs

@@ -1,18 +0,0 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
- * 
- * You can redistribute this program and/or modify it under the terms of
- * the GNU Lesser Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- */
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-
-namespace DiskAccessLibrary
-{
-    public abstract partial class DiskImage
-    {
-        public abstract void ExtendFast(long additionalNumberOfBytes);
-    }
-}

+ 0 - 42
DiskAccessLibrary/Win32/Disks/RawDiskImage/RawDiskImage.Win32.cs

@@ -1,42 +0,0 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
- * 
- * You can redistribute this program and/or modify it under the terms of
- * the GNU Lesser Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- */
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-
-namespace DiskAccessLibrary
-{
-    public partial class RawDiskImage : DiskImage
-    {
-        public override void ExtendFast(long additionalNumberOfBytes)
-        {
-            if (additionalNumberOfBytes % this.BytesPerSector > 0)
-            {
-                throw new ArgumentException("additionalNumberOfBytes must be a multiple of BytesPerSector");
-            }
-
-            long length = this.Size;
-            bool hasManageVolumePrivilege = SecurityUtils.ObtainManageVolumePrivilege();
-            FileStream stream = new FileStream(this.Path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 0x1000, FILE_FLAG_NO_BUFFERING | FileOptions.WriteThrough);
-            try
-            {
-                stream.SetLength(length + additionalNumberOfBytes);
-            }
-            catch
-            {
-                stream.Close();
-                throw;
-            }
-            if (hasManageVolumePrivilege)
-            {
-                FileStreamUtils.SetValidLength(stream, length + additionalNumberOfBytes);
-            }
-            stream.Close();
-        }
-    }
-}

+ 0 - 39
DiskAccessLibrary/Win32/Disks/VHD/VirtualHardDisk.Win32.cs

@@ -1,39 +0,0 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
- * 
- * You can redistribute this program and/or modify it under the terms of
- * the GNU Lesser Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- */
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using Utilities;
-using DiskAccessLibrary.VHD;
-
-namespace DiskAccessLibrary
-{
-    public partial class VirtualHardDisk : DiskImage, IDiskGeometry
-    {
-        public override void ExtendFast(long additionalNumberOfBytes)
-        {
-            if (additionalNumberOfBytes % this.BytesPerSector > 0)
-            {
-                throw new ArgumentException("additionalNumberOfBytes must be a multiple of BytesPerSector");
-            }
-
-            if (m_vhdFooter.DiskType == VirtualHardDiskType.Fixed)
-            {
-                long length = this.Size; // does not include the footer
-                m_file.ExtendFast(additionalNumberOfBytes);
-                m_vhdFooter.CurrentSize += (ulong)additionalNumberOfBytes;
-                byte[] footerBytes = m_vhdFooter.GetBytes();
-                m_file.WriteSectors((length + additionalNumberOfBytes) / this.BytesPerSector, footerBytes);
-            }
-            else
-            {
-                throw new NotImplementedException();
-            }
-        }
-    }
-}

+ 0 - 20
DiskAccessLibrary/Win32/Disks/VMDK/SparseExtent.Win32.cs

@@ -1,20 +0,0 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
- * 
- * You can redistribute this program and/or modify it under the terms of
- * the GNU Lesser Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- */
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace DiskAccessLibrary.VMDK
-{
-    public partial class SparseExtent
-    {
-        public override void ExtendFast(long additionalNumberOfBytes)
-        {
-            throw new NotImplementedException("The method or operation is not implemented.");
-        }
-    }
-}

+ 0 - 20
DiskAccessLibrary/Win32/Disks/VMDK/VirtualMachineDisk.Win32.cs

@@ -1,20 +0,0 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
- * 
- * You can redistribute this program and/or modify it under the terms of
- * the GNU Lesser Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- */
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace DiskAccessLibrary
-{
-    public partial class VirtualMachineDisk
-    {
-        public override void ExtendFast(long additionalNumberOfBytes)
-        {
-            throw new NotImplementedException("The method or operation is not implemented.");
-        }
-    }
-}