private static void ResumeAddDiskToRaid5Volume(List <DynamicDisk> disks, Raid5Volume volume, DynamicDiskExtent newExtent, AddDiskOperationBootRecord 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((decimal)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()); stripeIndexInVolume += numberOfStripesToTransfer; } // we're done, let's restore the filesystem boot record byte[] filesystemBootRecord = newExtent.ReadSector(newExtent.TotalSectors - 1); newVolume.WriteSectors(0, filesystemBootRecord); DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(disks, volume.DiskGroupGuid); VolumeManagerDatabaseHelper.ConvertStripedVolumeToRaid(database, volume.VolumeGuid); }