/// <returns>Number of bytes</returns> public static long GetMaximumSizeToExtendDynamicDiskExtent(DynamicDiskExtent targetExtent) { DynamicDisk disk = DynamicDisk.ReadFromDisk(targetExtent.Disk); PrivateHeader privateHeader = disk.PrivateHeader; List <DynamicDiskExtent> extents = DynamicDiskHelper.GetDiskExtents(disk); if (extents == null) { throw new InvalidDataException("Cannot read extents information from disk"); } long endOfData = (long)((privateHeader.PublicRegionStartLBA + privateHeader.PublicRegionSizeLBA) * (ulong)disk.BytesPerSector); long max = endOfData - (targetExtent.FirstSector * targetExtent.BytesPerSector + targetExtent.Size); // space from the extent end to the end of the disk foreach (DynamicDiskExtent extent in extents) { if (extent.FirstSector > targetExtent.FirstSector) { long spaceBetweenExtents = (extent.FirstSector - targetExtent.FirstSector) * disk.BytesPerSector - targetExtent.Size; max = Math.Min(max, spaceBetweenExtents); } } return(max); }
private void resumeOperationMenuItem_Click(object sender, EventArgs e) { DynamicVolume volume = (DynamicVolume)((KeyValuePair <Volume, DiskExtent>)extentContextMenu.Tag).Key; DynamicDiskExtent extent = (DynamicDiskExtent)((KeyValuePair <Volume, DiskExtent>)extentContextMenu.Tag).Value; List <DynamicDisk> dynamicDisks = GetDynamicDisks(); List <DynamicDisk> diskGroup = DynamicDiskHelper.FindDiskGroup(dynamicDisks, volume.DiskGroupGuid); ResumeForm resumeForm = new ResumeForm(diskGroup, volume, extent); DialogResult result = resumeForm.ShowDialog(); if (result == DialogResult.OK) { UpdateView(); if (Environment.OSVersion.Version.Major >= 6) { MessageBox.Show("Click OK to Continue", "Operation completed successfully"); } else { string message = OperatingSystemHelper.GetUpdateMessage(); MessageBox.Show(message, "Operation completed successfully"); } } }
private static void MoveExtentLeft(DiskGroupDatabase database, DynamicVolume volume, MoveExtentOperationResumeRecord resumeRecord, ref long bytesCopied) { DynamicDiskExtent relocatedExtent = DynamicDiskExtentHelper.GetByExtentID(volume.DynamicExtents, resumeRecord.ExtentID); if (resumeRecord.OldStartSector == (ulong)relocatedExtent.FirstSector) { // the database update was not completed (this must be a resume operation) relocatedExtent = new DynamicDiskExtent(relocatedExtent.Disk, (long)resumeRecord.NewStartSector, relocatedExtent.Size, resumeRecord.ExtentID); VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, relocatedExtent); } DiskExtent sourceExtent = new DiskExtent(relocatedExtent.Disk, (long)resumeRecord.OldStartSector, relocatedExtent.Size); MoveHelper.MoveExtentDataLeft(volume, sourceExtent, relocatedExtent, resumeRecord, ref bytesCopied); // if this is a resume, then volume is StripedVolume, otherwise it is a Raid5Volume if (resumeRecord.RestoreRAID5) { VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); // get the updated volume (we just reconverted to RAID-5) volume = DynamicVolumeHelper.GetVolumeByGuid(database.Disks, volume.VolumeGuid); } // restore the filesystem boot sector byte[] filesystemBootRecord = relocatedExtent.Disk.ReadSector((long)resumeRecord.BootRecordBackupSector); volume.WriteSectors(0, filesystemBootRecord); ClearBackupData(relocatedExtent.Disk, resumeRecord); }
private void addDiskToVolumeMenuItem_Click(object sender, EventArgs e) { DynamicVolume volume = (DynamicVolume)((KeyValuePair <Volume, DiskExtent>)extentContextMenu.Tag).Key; DynamicDiskExtent extent = (DynamicDiskExtent)((KeyValuePair <Volume, DiskExtent>)extentContextMenu.Tag).Value; List <DynamicDisk> dynamicDisks = GetDynamicDisks(); List <DynamicDisk> diskGroup = DynamicDiskHelper.FindDiskGroup(dynamicDisks, volume.DiskGroupGuid); AddDiskForm addDisk = new AddDiskForm(diskGroup, volume); DialogResult result = addDisk.ShowDialog(); if (result == DialogResult.OK) { if (Environment.OSVersion.Version.Major >= 6) { // Windows 7 / 2008 will likely make changes to the disk group, it will be marked as 'dirty' if we don't wait Thread.Sleep(Windows6WaitTimeBeforeRefresh); } UpdateView(); if (Environment.OSVersion.Version.Major >= 6) { //MessageBox.Show("Please go to Disk Management and reactivate the disk group", "Operation completed successfully"); MessageBox.Show("The volume has been extended successfully.\nyou can now proceed to extend the underlying file system.", "Operation completed successfully"); } else { string message = "The volume has been extended successfully.\nyou can now proceed to extend the underlying file system."; message += "\n\n" + OperatingSystemHelper.GetUpdateMessage(); MessageBox.Show(message, "Operation completed successfully"); } } }
public const int BackupBufferSizeLBA = 128; // there are about 180 contiguous free sectors in a private region /// <summary> /// Move extent to another disk /// </summary> public static void MoveExtentToAnotherDisk(DiskGroupDatabase database, DynamicVolume volume, DynamicDiskExtent sourceExtent, DiskExtent relocatedExtent, ref long bytesCopied) { // copy the data long transferSizeLBA = Settings.MaximumTransferSizeLBA; for (long sectorIndex = 0; sectorIndex < relocatedExtent.TotalSectors; sectorIndex += transferSizeLBA) { long sectorsLeft = relocatedExtent.TotalSectors - sectorIndex; int sectorsToRead = (int)Math.Min(transferSizeLBA, sectorsLeft); byte[] data = sourceExtent.ReadSectors(sectorIndex, sectorsToRead); relocatedExtent.WriteSectors(sectorIndex, data); bytesCopied += sectorsToRead * sourceExtent.BytesPerSector; } // Update the database to point to the relocated extent DynamicDisk targetDisk = DynamicDisk.ReadFromDisk(relocatedExtent.Disk); DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID); dynamicRelocatedExtent.Name = sourceExtent.Name; dynamicRelocatedExtent.DiskGuid = targetDisk.DiskGuid; VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent); }
private static void MoveExtentRight(DiskGroupDatabase database, DynamicVolume volume, MoveExtentOperationResumeRecord resumeRecord, ref long bytesCopied) { DynamicDiskExtent sourceExtent = GetVolumeExtent(volume, resumeRecord.ExtentID); DiskExtent relocatedExtent = new DiskExtent(sourceExtent.Disk, (long)resumeRecord.NewStartSector, sourceExtent.Size); MoveHelper.MoveExtentDataRight(volume, sourceExtent, relocatedExtent, resumeRecord, ref bytesCopied); // even if the database update won't complete, the resume record was copied // update the database DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID); dynamicRelocatedExtent.Name = sourceExtent.Name; dynamicRelocatedExtent.DiskGuid = sourceExtent.DiskGuid; VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent); // if this is a resume, then volume is StripedVolume, otherwise it is a Raid5Volume if (resumeRecord.RestoreRAID5) { VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); } // get the updated volume (we moved an extent and possibly reconverted to RAID-5) volume = DynamicVolumeHelper.GetVolumeByGuid(database.Disks, volume.VolumeGuid); // restore the filesystem boot sector byte[] filesystemBootRecord = relocatedExtent.Disk.ReadSector((long)resumeRecord.BootRecordBackupSector); volume.WriteSectors(0, filesystemBootRecord); ClearBackupData(relocatedExtent.Disk, resumeRecord); }
public static string GetExtentsInformation(DynamicVolume volume) { List <DynamicDiskExtent> extents = volume.DynamicExtents; StringBuilder builder = new StringBuilder(); for (int extentIndex = 0; extentIndex < extents.Count; extentIndex++) { DynamicDiskExtent extent = extents[extentIndex]; string extentOffsetString; string diskIDString = String.Empty; if (extent.Disk != null) { long extentOffset = extent.FirstSector * extent.Disk.BytesPerSector; extentOffsetString = FormattingHelper.GetStandardSizeString(extentOffset); VolumeManagerDatabase database = VolumeManagerDatabase.ReadFromDisk(extent.Disk); if (database != null) { ExtentRecord extentRecord = database.FindExtentByExtentID(extent.ExtentID); if (extentRecord != null) { diskIDString = extentRecord.DiskId.ToString(); } } } else { extentOffsetString = "N/A"; } string extentSizeString = FormattingHelper.GetStandardSizeString(extent.Size); builder.AppendFormat("Extent {0}, ID: {1}, Name: {2}, Size: {3}, Disk ID: {4}, Offset: {5}, Start Sector: {6}\n", extentIndex, extent.ExtentID, extent.Name, extentSizeString, diskIDString, extentOffsetString, extent.FirstSector); } return(builder.ToString()); }
public ResumeForm(List <DynamicDisk> diskGroup, DynamicVolume volume, DynamicDiskExtent extent) { InitializeComponent(); m_diskGroup = diskGroup; m_volume = volume; m_extent = extent; }
private void ResumeMove(MoveExtentOperationResumeRecord resumeRecord) { DiskGroupLockResult result = DiskGroupHelper.LockDiskGroup(m_diskGroup); if (result == DiskGroupLockResult.CannotLockDisk) { MessageBox.Show("Unable to lock all disks!", "Error"); } else if (result == DiskGroupLockResult.CannotLockVolume) { MessageBox.Show("Unable to lock all volumes!", "Error"); } else if (result == DiskGroupLockResult.OneOrMoreDisksAreOfflineOrReadonly) { MessageBox.Show("One or more disks are offline or set to readonly.", "Error"); } else if (result == DiskGroupLockResult.CannotTakeDiskOffline) { MessageBox.Show("Failed to take all dynamic disks offline!", "Error"); } else if (result == DiskGroupLockResult.Success) { int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(m_volume.DynamicExtents, (resumeRecord).ExtentID); DynamicDiskExtent sourceExtent = m_volume.DynamicExtents[extentIndex]; long bytesTotal = sourceExtent.Size; long bytesCopied = 0; Thread workerThread = new Thread(delegate() { m_isWorking = true; List <DynamicDisk> diskGroup = WindowsDynamicDiskHelper.GetPhysicalDynamicDisks(m_volume.DiskGroupGuid); DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(diskGroup, m_volume.DiskGroupGuid); MoveExtentHelper.ResumeMoveExtent(database, m_volume, resumeRecord, ref bytesCopied); m_isWorking = false; }); workerThread.Start(); new Thread(delegate() { while (workerThread.IsAlive) { Thread.Sleep(250); int progress = (int)(100 * (double)bytesCopied / bytesTotal); this.Invoke((MethodInvoker) delegate() { progressBar.Value = progress; }); } this.Invoke((MethodInvoker) delegate() { DiskGroupHelper.UnlockDiskGroup(m_diskGroup); this.DialogResult = DialogResult.OK; this.Close(); }); }).Start(); } }
/// <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); }
private void moveExtentMenuItem_Click(object sender, EventArgs e) { DynamicVolume volume = (DynamicVolume)((KeyValuePair <Volume, DiskExtent>)extentContextMenu.Tag).Key; DynamicDiskExtent extent = (DynamicDiskExtent)((KeyValuePair <Volume, DiskExtent>)extentContextMenu.Tag).Value; List <DynamicDisk> dynamicDisks = GetDynamicDisks(); List <DynamicDisk> diskGroup = DynamicDiskHelper.FindDiskGroup(dynamicDisks, volume.DiskGroupGuid); bool isBootVolume; if (RetainHelper.IsVolumeRetained(volume, out isBootVolume)) { StringBuilder builder = new StringBuilder(); builder.AppendLine("You're trying to move a retained volume (volume that has a partition"); builder.AppendLine("associated with it)."); builder.AppendLine("If an operating system is present on this volume, a reconfiguration"); builder.AppendLine("might be necessary before you could boot it successfully."); builder.AppendLine("This operation is currently not supported."); MessageBox.Show(builder.ToString(), "Warning"); return; } if (DynamicDiskPartitionerResumeRecord.HasValidSignature(volume.ReadSector(0))) { StringBuilder builder = new StringBuilder(); builder.AppendLine("There is already an operation in progress"); builder.AppendLine("Use the RESUME command to resume the operation"); MessageBox.Show(builder.ToString(), "Error"); return; } MoveExtentForm moveExtent = new MoveExtentForm(diskGroup, volume, extent); DialogResult result = moveExtent.ShowDialog(); if (result == DialogResult.OK) { if (Environment.OSVersion.Version.Major >= 6) { // Windows 7 / 2008 will likely make changes to the disk group, it will be marked as 'dirty' if we don't wait Thread.Sleep(Windows6WaitTimeBeforeRefresh); } UpdateView(); if (Environment.OSVersion.Version.Major >= 6) { //MessageBox.Show("Please go to Disk Management and reactivate the disk group", "Operation completed successfully"); MessageBox.Show("Click OK to Continue", "Operation completed successfully"); } else { string message = OperatingSystemHelper.GetUpdateMessage(); MessageBox.Show(message, "Operation completed successfully"); } } }
public static void ResumeAddDiskToRaid5Volume(DiskGroupDatabase database, StripedVolume stripedVolume, AddDiskOperationResumeRecord resumeRecord, ref long bytesCopied) { List <DynamicColumn> columns = stripedVolume.Columns; DynamicDiskExtent newExtent = columns[columns.Count - 1].Extents[0]; columns.RemoveAt(columns.Count - 1); Raid5Volume volume = new Raid5Volume(columns, stripedVolume.SectorsPerStripe, stripedVolume.VolumeGuid, stripedVolume.DiskGroupGuid); volume.VolumeID = stripedVolume.VolumeID; volume.Name = stripedVolume.Name; volume.DiskGroupName = stripedVolume.DiskGroupName; ResumeAddDiskToRaid5Volume(database, volume, newExtent, resumeRecord, ref bytesCopied); }
/// <summary> /// Segment - sequence of stripes that is a multiple of (NumberOfColumns - 1), /// and every (NumberOfColumns - 1) stripes in the sequence have the same stripeIndexInColumn /// (Such sequence can be written to disk without reading the parity information first) /// </summary> public static void WriteSegment(Raid5Volume volume, DynamicDiskExtent newExtent, long firstStripeIndexInColumn, byte[] data) { List <DynamicColumn> newArray = new List <DynamicColumn>(); newArray.AddRange(volume.Columns); newArray.Add(new DynamicColumn(newExtent)); int bytesPerStripe = volume.BytesPerStripe; int stripesToWritePerColumn = (data.Length / bytesPerStripe) / (newArray.Count - 1); int dataLengthPerColumn = stripesToWritePerColumn * bytesPerStripe; byte[][] columnData = new byte[newArray.Count][]; for (int index = 0; index < columnData.Length; index++) { columnData[index] = new byte[dataLengthPerColumn]; } Parallel.For(0, stripesToWritePerColumn, delegate(int stripeOffsetInColumn) { long stripeIndexInColumn = firstStripeIndexInColumn + stripeOffsetInColumn; int parityColumnIndex = (newArray.Count - 1) - (int)(stripeIndexInColumn % newArray.Count); byte[] parityData = new byte[bytesPerStripe]; for (int stripeVerticalIndex = 0; stripeVerticalIndex < newArray.Count - 1; stripeVerticalIndex++) { int columnIndex = (parityColumnIndex + 1 + stripeVerticalIndex) % newArray.Count; long stripeOffsetInData = (stripeOffsetInColumn * (newArray.Count - 1) + stripeVerticalIndex) * bytesPerStripe; Array.Copy(data, stripeOffsetInData, columnData[columnIndex], stripeOffsetInColumn * bytesPerStripe, bytesPerStripe); parityData = ByteUtils.XOR(parityData, 0, columnData[columnIndex], stripeOffsetInColumn * bytesPerStripe, bytesPerStripe); } Array.Copy(parityData, 0, columnData[parityColumnIndex], stripeOffsetInColumn * bytesPerStripe, bytesPerStripe); }); // write the data long firstSectorIndexInColumn = firstStripeIndexInColumn * volume.SectorsPerStripe; for (int columnIndex = 0; columnIndex < newArray.Count; columnIndex++) { newArray[columnIndex].WriteSectors(firstSectorIndexInColumn, columnData[columnIndex]); } }
/// <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 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); extent.Name = extentRecord.Name; extent.DiskGuid = diskGuid; return(extent); }
public static void ResumeMoveExtent(DiskGroupDatabase database, DynamicVolume volume, MoveExtentOperationResumeRecord resumeRecord, ref long bytesCopied) { if (resumeRecord.OldStartSector == resumeRecord.NewStartSector) { throw new InvalidDataException("Invalid move record"); } if (resumeRecord.RestoreFromBuffer) { // we need to use the backup buffer to restore the data that may have been overwritten DynamicDiskExtent sourceExtent = GetVolumeExtent(volume, resumeRecord.ExtentID); byte[] backupBuffer = sourceExtent.Disk.ReadSectors((long)resumeRecord.BackupBufferStartSector, BackupBufferSizeLBA); if (resumeRecord.OldStartSector < resumeRecord.NewStartSector) { // move right long readCount = (long)resumeRecord.NumberOfCommittedSectors; int sectorsToRead = BackupBufferSizeLBA; long sectorIndex = sourceExtent.TotalSectors - readCount - sectorsToRead; sourceExtent.WriteSectors(sectorIndex, backupBuffer); System.Diagnostics.Debug.WriteLine("Restored to " + sectorIndex); } else { // move left long sectorIndex = (long)resumeRecord.NumberOfCommittedSectors; sourceExtent.WriteSectors(sectorIndex, backupBuffer); System.Diagnostics.Debug.WriteLine("Restored to " + sectorIndex); } } if (resumeRecord.OldStartSector < resumeRecord.NewStartSector) { MoveExtentRight(database, volume, resumeRecord, ref bytesCopied); } else { MoveExtentLeft(database, volume, resumeRecord, ref bytesCopied); } }
private static List <DynamicColumn> GetDynamicVolumeColumns(List <DynamicDisk> disks, VolumeManagerDatabase database, ComponentRecord componentRecord, VolumeRecord volumeRecord) { // extentRecords are sorted by offset in column List <ExtentRecord> extentRecords = database.FindExtentsByComponentID(componentRecord.ComponentId); if (componentRecord.NumberOfExtents != extentRecords.Count || extentRecords.Count == 0) { // database record is invalid throw new InvalidDataException("Number of extents in component record does not match actual number of extent records"); } SortedList <uint, List <DynamicDiskExtent> > columns = new SortedList <uint, List <DynamicDiskExtent> >(); foreach (ExtentRecord extentRecord in extentRecords) { DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); DynamicDisk disk = DynamicDiskHelper.FindDisk(disks, diskRecord.DiskGuid); // we add nulls as well DynamicDiskExtent extent = DynamicDiskExtentHelper.GetDiskExtent(disk, extentRecord); if (columns.ContainsKey(extentRecord.ColumnIndex)) { columns[extentRecord.ColumnIndex].Add(extent); } else { List <DynamicDiskExtent> list = new List <DynamicDiskExtent>(); list.Add(extent); columns.Add(extentRecord.ColumnIndex, list); } } List <DynamicColumn> result = new List <DynamicColumn>(); foreach (List <DynamicDiskExtent> extents in columns.Values) { result.Add(new DynamicColumn(extents)); } 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); }
private static SimpleVolume GetSimpleVolume(List <DynamicDisk> disks, VolumeManagerDatabase database, ComponentRecord componentRecord, VolumeRecord volumeRecord) { List <ExtentRecord> extentRecords = database.FindExtentsByComponentID(componentRecord.ComponentId); if (extentRecords.Count == 1) { ExtentRecord extentRecord = extentRecords[0]; DiskRecord diskRecord = database.FindDiskByDiskID(extentRecord.DiskId); DynamicDisk disk = DynamicDiskHelper.FindDisk(disks, diskRecord.DiskGuid); // we add nulls as well DynamicDiskExtent extent = DynamicDiskExtentHelper.GetDiskExtent(disk, extentRecord); SimpleVolume volume = new SimpleVolume(extent, volumeRecord.VolumeGuid, database.DiskGroupGuid); volume.VolumeID = volumeRecord.VolumeId; volume.Name = volumeRecord.Name; volume.DiskGroupName = database.DiskGroupName; return(volume); } else { // component / extent records are invalid throw new InvalidDataException("Number of extents in component record does not match actual number of extent records"); } }
private static void ResumeAddDiskToRaid5Volume(DiskGroupDatabase database, Raid5Volume volume, DynamicDiskExtent newExtent, AddDiskOperationResumeRecord resumeRecord, ref long bytesCopied) { // When reading from the volume, we must use the old volume (without the new disk) // However, when writing the boot sector to the volume, we must use the new volume or otherwise parity information will be invalid List <DynamicColumn> newVolumeColumns = new List <DynamicColumn>(); newVolumeColumns.AddRange(volume.Columns); newVolumeColumns.Add(new DynamicColumn(newExtent)); Raid5Volume newVolume = new Raid5Volume(newVolumeColumns, volume.SectorsPerStripe, volume.VolumeGuid, volume.DiskGroupGuid); int oldColumnCount = volume.Columns.Count; int newColumnCount = oldColumnCount + 1; long resumeFromStripe = (long)resumeRecord.NumberOfCommittedSectors / volume.SectorsPerStripe; // it would be prudent to write the new extent before committing to the operation, however, it would take much longer. // The number of sectors in extent / column is always a multiple of SectorsPerStripe. // We read enough stripes to write a vertical stripe segment in the new array, // We will read MaximumTransferSizeLBA and make sure maximumStripesToTransfer is multiple of (NumberOfColumns - 1). int maximumStripesToTransfer = (Settings.MaximumTransferSizeLBA / volume.SectorsPerStripe) / (newColumnCount - 1) * (newColumnCount - 1); long totalStripesInVolume = volume.TotalStripes; long stripeIndexInVolume = resumeFromStripe; while (stripeIndexInVolume < totalStripesInVolume) { // When we add a column, the distance between the stripes we read (later in the column) to thes one we write (earlier), // Is growing constantly (because we can stack more stripes in each vertical stripe), so we increment the number of stripes we // can safely transfer as we go. // (We assume that the segment we write will be corrupted if there will be a power failure) long stripeToReadIndexInColumn = stripeIndexInVolume / (oldColumnCount - 1); long stripeToWriteIndexInColumn = stripeIndexInVolume / (newColumnCount - 1); long numberOfStripesSafeToTransfer = (stripeToReadIndexInColumn - stripeToWriteIndexInColumn) * (newColumnCount - 1); bool verticalStripeAtRisk = (numberOfStripesSafeToTransfer == 0); if (numberOfStripesSafeToTransfer == 0) { // The first few stripes in each column are 'at rist', meaning that we may overwrite crucial data (that is only stored in memory) // when writing the segment that will be lost forever if a power failure will occur during the write operation. // Note: The number of 'at risk' vertical stripes is equal to the number of columns in the old array - 1 numberOfStripesSafeToTransfer = (newColumnCount - 1); } int numberOfStripesToTransfer = (int)Math.Min(numberOfStripesSafeToTransfer, maximumStripesToTransfer); long stripesLeft = totalStripesInVolume - stripeIndexInVolume; numberOfStripesToTransfer = (int)Math.Min(numberOfStripesToTransfer, stripesLeft); byte[] segmentData = volume.ReadStripes(stripeIndexInVolume, numberOfStripesToTransfer); if (numberOfStripesToTransfer % (newColumnCount - 1) > 0) { // this is the last segment and we need to zero-fill it for the write: int numberOfStripesToWrite = (int)Math.Ceiling((double)numberOfStripesToTransfer / (newColumnCount - 1)) * (newColumnCount - 1); byte[] temp = new byte[numberOfStripesToWrite * volume.BytesPerStripe]; Array.Copy(segmentData, temp, segmentData.Length); segmentData = temp; } long firstStripeIndexInColumn = stripeIndexInVolume / (newColumnCount - 1); if (verticalStripeAtRisk) { // we write 'at risk' stripes one at a time to the new volume, this will make sure they will not overwrite crucial data // (because they will be written in an orderly fashion, and not in bulk from the first column to the last) newVolume.WriteStripes(stripeIndexInVolume, segmentData); } else { WriteSegment(volume, newExtent, firstStripeIndexInColumn, segmentData); } // update resume record resumeRecord.NumberOfCommittedSectors += (ulong)(numberOfStripesToTransfer * volume.SectorsPerStripe); bytesCopied = (long)resumeRecord.NumberOfCommittedSectors * volume.BytesPerSector; newVolume.WriteSectors(0, resumeRecord.GetBytes(newVolume.BytesPerSector)); stripeIndexInVolume += numberOfStripesToTransfer; } // we're done, let's restore the filesystem boot record byte[] filesystemBootRecord = newExtent.ReadSector(newExtent.TotalSectors - 1); newVolume.WriteSectors(0, filesystemBootRecord); VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); }
/// <summary> /// Move extent to a new location on the same disk /// </summary> public static void MoveExtentWithinSameDisk(DiskGroupDatabase database, DynamicVolume volume, DynamicDiskExtent sourceExtent, DiskExtent relocatedExtent, ref long bytesCopied) { MoveExtentOperationResumeRecord resumeRecord = new MoveExtentOperationResumeRecord(); // If there will be a power failure during the move, a RAID volume will resync during boot, // To prevent destruction of the data, we temporarily convert the array to striped volume if (volume is Raid5Volume) { VolumeManagerDatabaseHelper.ConvertRaidToStripedVolume(database, volume.VolumeGuid); resumeRecord.RestoreRAID5 = true; } // We want to write our own volume boot sector for recovery purposes, so we must find where to backup the old boot sector. // We don't want to store the backup in the range of the existing or relocated extent, because then we would have to move // the backup around during the move operation, other options include: // 1. Store it between sectors 1-62 (cons: Could be in use, Windows occasionally start a volume from sector 1) // 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. // 3. use the LDM private region to store the sector. DynamicDisk dynamicDisk = DynamicDisk.ReadFromDisk(relocatedExtent.Disk); // Note: backupSectorIndex will be from the beginning of the private region while backupBufferStartSector will be from the end // so there is no need to allocate them long backupSectorIndex = PrivateRegionHelper.FindUnusedSector(dynamicDisk.PrivateHeader, dynamicDisk.TOCBlock); resumeRecord.VolumeGuid = volume.VolumeGuid; resumeRecord.NumberOfCommittedSectors = 0; resumeRecord.ExtentID = sourceExtent.ExtentID; resumeRecord.OldStartSector = (ulong)sourceExtent.FirstSector; resumeRecord.NewStartSector = (ulong)relocatedExtent.FirstSector; resumeRecord.BootRecordBackupSector = (ulong)backupSectorIndex; long distanceLBA = (long)Math.Abs((double)resumeRecord.NewStartSector - resumeRecord.OldStartSector); if (distanceLBA < MoveHelper.BufferedModeThresholdLBA) { long backupBufferStartSector = PrivateRegionHelper.FindUnusedRegion(dynamicDisk.PrivateHeader, dynamicDisk.TOCBlock, BackupBufferSizeLBA); if (backupBufferStartSector == -1) { throw new Exception("Private region is full"); } if (backupBufferStartSector <= backupSectorIndex) { throw new Exception("Private region structure is unknown"); } resumeRecord.BackupBufferStartSector = (ulong)backupBufferStartSector; resumeRecord.BackupBufferSizeLBA = BackupBufferSizeLBA; } // Backup the first sector of the first extent // (We replace the filesystem boot record with our own sector for recovery purposes) byte[] filesystemBootRecord = volume.ReadSector(0); relocatedExtent.Disk.WriteSectors(backupSectorIndex, filesystemBootRecord); // we write the resume record instead of the boot record volume.WriteSectors(0, resumeRecord.GetBytes(volume.BytesPerSector)); if (sourceExtent.FirstSector < relocatedExtent.FirstSector) { // move right MoveExtentRight(database, volume, resumeRecord, ref bytesCopied); } else { // move left // 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) relocatedExtent.WriteSectors(0, resumeRecord.GetBytes(relocatedExtent.BytesPerSector)); DynamicDiskExtent dynamicRelocatedExtent = new DynamicDiskExtent(relocatedExtent, sourceExtent.ExtentID); dynamicRelocatedExtent.Name = sourceExtent.Name; dynamicRelocatedExtent.DiskGuid = sourceExtent.DiskGuid; VolumeManagerDatabaseHelper.UpdateExtentLocation(database, volume, dynamicRelocatedExtent); int extentIndex = DynamicDiskExtentHelper.GetIndexOfExtentID(volume.DynamicExtents, sourceExtent.ExtentID); // get the updated volume (we just moved an extent) volume = DynamicVolumeHelper.GetVolumeByGuid(database.Disks, volume.VolumeGuid); MoveExtentLeft(database, volume, resumeRecord, ref bytesCopied); } }