MoveExtentHelper.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Text;
  11. using Utilities;
  12. using DiskAccessLibrary;
  13. namespace DiskAccessLibrary.LogicalDiskManager
  14. {
  15. public class MoveExtentHelper
  16. {
  17. public const int BackupBufferSizeLBA = 128; // there are about 180 contiguous free sectors in a private region
  18. /// <summary>
  19. /// Move extent to another disk
  20. /// </summary>
  21. public static void MoveExtentToAnotherDisk(List<DynamicDisk> disks, DynamicVolume volume, DynamicDiskExtent sourceExtent, DiskExtent relocatedExtent, ref long bytesCopied)
  22. {
  23. DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid);
  24. if (database == null)
  25. {
  26. throw new DatabaseNotFoundException();
  27. }
  28. // copy the data
  29. long transferSizeLBA = Settings.MaximumTransferSizeLBA;
  30. for (long sectorIndex = 0; sectorIndex < relocatedExtent.TotalSectors; sectorIndex += transferSizeLBA)
  31. {
  32. long sectorsLeft = relocatedExtent.TotalSectors - sectorIndex;
  33. int sectorsToRead = (int)Math.Min(transferSizeLBA, sectorsLeft);
  34. byte[] data = sourceExtent.ReadSectors(sectorIndex, sectorsToRead);
  35. relocatedExtent.WriteSectors(sectorIndex, data);
  36. bytesCopied += sectorsToRead * sourceExtent.BytesPerSector;
  37. }
  38. // Update the database to point to the relocated extent
  39. DynamicDisk targetDisk = DynamicDisk.ReadFromDisk(relocatedExtent.Disk);
  40. DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID);
  41. dynamicRelocatedExtent.Name = sourceExtent.Name;
  42. dynamicRelocatedExtent.DiskGuid = targetDisk.DiskGuid;
  43. VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent);
  44. }
  45. /// <summary>
  46. /// Move extent to a new location on the same disk
  47. /// </summary>
  48. public static void MoveExtentWithinSameDisk(List<DynamicDisk> disks, DynamicVolume volume, DynamicDiskExtent sourceExtent, DiskExtent relocatedExtent, ref long bytesCopied)
  49. {
  50. DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid);
  51. if (database == null)
  52. {
  53. throw new DatabaseNotFoundException();
  54. }
  55. MoveExtentOperationBootRecord resumeRecord = new MoveExtentOperationBootRecord();
  56. // If there will be a power failure during the move, a RAID volume will resync during boot,
  57. // To prevent destruction of the data, we temporarily convert the array to striped volume
  58. if (volume is Raid5Volume)
  59. {
  60. VolumeManagerDatabaseHelper.ConvertRaidToStripedVolume(database, volume.VolumeGuid);
  61. resumeRecord.RestoreRAID5 = true;
  62. }
  63. // We want to write our own volume boot sector for recovery purposes, so we must find where to backup the old boot sector.
  64. // We don't want to store the backup in the range of the existing or relocated extent, because then we would have to move
  65. // the backup around during the move operation, other options include:
  66. // 1. Store it between sectors 1-62 (cons: Could be in use, Windows occasionally start a volume from sector 1)
  67. // 2. Find an easily compressible sector (e.g. zero-filled) within the existing extent, overwrite it with the backup, and restore it when the operation is done.
  68. // 3. use the LDM private region to store the sector.
  69. DynamicDisk dynamicDisk = DynamicDisk.ReadFromDisk(relocatedExtent.Disk);
  70. // Note: backupSectorIndex will be from the beginning of the private region while backupBufferStartSector will be from the end
  71. // so there is no need to allocate them
  72. long backupSectorIndex = DynamicDiskHelper.FindUnusedSectorInPrivateRegion(dynamicDisk);
  73. resumeRecord.VolumeGuid = volume.VolumeGuid;
  74. resumeRecord.NumberOfCommittedSectors = 0;
  75. resumeRecord.ExtentID = sourceExtent.ExtentID;
  76. resumeRecord.OldStartSector = (ulong)sourceExtent.FirstSector;
  77. resumeRecord.NewStartSector = (ulong)relocatedExtent.FirstSector;
  78. resumeRecord.BootRecordBackupSector = (ulong)backupSectorIndex;
  79. long distanceLBA = (long)Math.Abs((double)resumeRecord.NewStartSector - resumeRecord.OldStartSector);
  80. if (distanceLBA < MoveHelper.BufferedModeThresholdLBA)
  81. {
  82. long backupBufferStartSector = DynamicDiskHelper.FindUnusedRegionInPrivateRegion(dynamicDisk, BackupBufferSizeLBA);
  83. if (backupBufferStartSector == -1)
  84. {
  85. throw new Exception("Private region is full");
  86. }
  87. if (backupBufferStartSector <= backupSectorIndex)
  88. {
  89. throw new Exception("Private region structure is unknown");
  90. }
  91. resumeRecord.BackupBufferStartSector = (ulong)backupBufferStartSector;
  92. resumeRecord.BackupBufferSizeLBA = BackupBufferSizeLBA;
  93. }
  94. // Backup the first sector of the first extent
  95. // (We replace the filesystem boot record with our own sector for recovery purposes)
  96. byte[] filesystemBootRecord = volume.ReadSector(0);
  97. relocatedExtent.Disk.WriteSectors(backupSectorIndex, filesystemBootRecord);
  98. // we write the resume record instead of the boot record
  99. volume.WriteSectors(0, resumeRecord.GetBytes());
  100. if (sourceExtent.FirstSector < relocatedExtent.FirstSector)
  101. {
  102. // move right
  103. MoveExtentRight(disks, volume, resumeRecord, ref bytesCopied);
  104. }
  105. else
  106. {
  107. // move left
  108. // we write the resume record at the new location as well (to be able to resume if a power failure will occur immediately after updating the database)
  109. relocatedExtent.WriteSectors(0, resumeRecord.GetBytes());
  110. DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID);
  111. dynamicRelocatedExtent.Name = sourceExtent.Name;
  112. dynamicRelocatedExtent.DiskGuid = sourceExtent.DiskGuid;
  113. VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent);
  114. int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(volume.DynamicExtents, sourceExtent.ExtentID);
  115. // get the updated volume (we just moved an extent)
  116. volume = DynamicVolumeHelper.GetVolumeByGuid(disks, volume.VolumeGuid);
  117. MoveExtentLeft(disks, volume, resumeRecord, ref bytesCopied);
  118. }
  119. }
  120. public static void ResumeMoveExtent(List<DynamicDisk> disks, DynamicVolume volume, MoveExtentOperationBootRecord resumeRecord, ref long bytesCopied)
  121. {
  122. if (resumeRecord.OldStartSector == resumeRecord.NewStartSector)
  123. {
  124. throw new InvalidDataException("Invalid move record");
  125. }
  126. if (resumeRecord.RestoreFromBuffer)
  127. {
  128. // we need to use the backup buffer to restore the data that may have been overwritten
  129. int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(volume.DynamicExtents, resumeRecord.ExtentID);
  130. DynamicDiskExtent sourceExtent = volume.DynamicExtents[extentIndex];
  131. byte[] backupBuffer = sourceExtent.Disk.ReadSectors((long)resumeRecord.BackupBufferStartSector, BackupBufferSizeLBA);
  132. if (resumeRecord.OldStartSector < resumeRecord.NewStartSector)
  133. {
  134. // move right
  135. long readCount = (long)resumeRecord.NumberOfCommittedSectors;
  136. int sectorsToRead = BackupBufferSizeLBA;
  137. long sectorIndex = sourceExtent.TotalSectors - readCount - sectorsToRead;
  138. sourceExtent.WriteSectors(sectorIndex, backupBuffer);
  139. System.Diagnostics.Debug.WriteLine("Restored to " + sectorIndex);
  140. }
  141. else
  142. {
  143. // move left
  144. long sectorIndex = (long)resumeRecord.NumberOfCommittedSectors;
  145. sourceExtent.WriteSectors(sectorIndex, backupBuffer);
  146. System.Diagnostics.Debug.WriteLine("Restored to " + sectorIndex);
  147. }
  148. }
  149. if (resumeRecord.OldStartSector < resumeRecord.NewStartSector)
  150. {
  151. MoveExtentRight(disks, volume, resumeRecord, ref bytesCopied);
  152. }
  153. else
  154. {
  155. MoveExtentLeft(disks, volume, resumeRecord, ref bytesCopied);
  156. }
  157. }
  158. private static void MoveExtentRight(List<DynamicDisk> disks, DynamicVolume volume, MoveExtentOperationBootRecord resumeRecord, ref long bytesCopied)
  159. {
  160. DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid);
  161. if (database == null)
  162. {
  163. throw new DatabaseNotFoundException();
  164. }
  165. int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(volume.DynamicExtents, resumeRecord.ExtentID);
  166. DynamicDiskExtent sourceExtent = volume.DynamicExtents[extentIndex];
  167. DiskExtent relocatedExtent = new DiskExtent(sourceExtent.Disk, (long)resumeRecord.NewStartSector, sourceExtent.Size);
  168. MoveHelper.MoveExtentDataRight(volume, sourceExtent, relocatedExtent, resumeRecord, ref bytesCopied);
  169. // even if the database update won't complete, the resume record was copied
  170. // update the database
  171. DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID);
  172. dynamicRelocatedExtent.Name = sourceExtent.Name;
  173. dynamicRelocatedExtent.DiskGuid = sourceExtent.DiskGuid;
  174. VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent);
  175. // if this is a resume, then volume is StripedVolume, otherwise it is a Raid5Volume
  176. if (resumeRecord.RestoreRAID5)
  177. {
  178. VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid);
  179. }
  180. // get the updated volume (we moved an extent and possibly reconverted to RAID-5)
  181. volume = DynamicVolumeHelper.GetVolumeByGuid(disks, volume.VolumeGuid);
  182. // restore the filesystem boot sector
  183. byte[] filesystemBootRecord = relocatedExtent.Disk.ReadSector((long)resumeRecord.BootRecordBackupSector);
  184. volume.WriteSectors(0, filesystemBootRecord);
  185. ClearBackupData(relocatedExtent.Disk, resumeRecord);
  186. }
  187. private static void MoveExtentLeft(List<DynamicDisk> disks, DynamicVolume volume, MoveExtentOperationBootRecord resumeRecord, ref long bytesCopied)
  188. {
  189. DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid);
  190. if (database == null)
  191. {
  192. throw new DatabaseNotFoundException();
  193. }
  194. DynamicDiskExtent relocatedExtent = DynamicDiskExtentHelper.GetByExtentID(volume.DynamicExtents, resumeRecord.ExtentID);
  195. if (resumeRecord.OldStartSector == (ulong)relocatedExtent.FirstSector)
  196. {
  197. // the database update was not completed (this must be a resume operation)
  198. relocatedExtent = new DynamicDiskExtent(relocatedExtent.Disk, (long)resumeRecord.NewStartSector, relocatedExtent.Size, resumeRecord.ExtentID);
  199. VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, relocatedExtent);
  200. }
  201. DiskExtent sourceExtent = new DiskExtent(relocatedExtent.Disk, (long)resumeRecord.OldStartSector, relocatedExtent.Size);
  202. MoveHelper.MoveExtentDataLeft(volume, sourceExtent, relocatedExtent, resumeRecord, ref bytesCopied);
  203. // if this is a resume, then volume is StripedVolume, otherwise it is a Raid5Volume
  204. if (resumeRecord.RestoreRAID5)
  205. {
  206. VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid);
  207. // get the updated volume (we just reconverted to RAID-5)
  208. volume = DynamicVolumeHelper.GetVolumeByGuid(disks, volume.VolumeGuid);
  209. }
  210. // restore the filesystem boot sector
  211. byte[] filesystemBootRecord = relocatedExtent.Disk.ReadSector((long)resumeRecord.BootRecordBackupSector);
  212. volume.WriteSectors(0, filesystemBootRecord);
  213. ClearBackupData(relocatedExtent.Disk, resumeRecord);
  214. }
  215. private static void ClearBackupData(Disk relocatedExtentDisk, MoveExtentOperationBootRecord resumeRecord)
  216. {
  217. byte[] emptySector = new byte[relocatedExtentDisk.BytesPerSector];
  218. relocatedExtentDisk.WriteSectors((long)resumeRecord.BootRecordBackupSector, emptySector);
  219. if (resumeRecord.BackupBufferStartSector > 0)
  220. {
  221. byte[] emptyRegion = new byte[resumeRecord.BackupBufferSizeLBA * relocatedExtentDisk.BytesPerSector];
  222. relocatedExtentDisk.WriteSectors((long)resumeRecord.BackupBufferStartSector, emptyRegion);
  223. }
  224. }
  225. }
  226. }