public static void AddDiskToRaid5Volume(DiskGroupDatabase database, Raid5Volume volume, DiskExtent newExtent, ref long bytesCopied)
        {
            // If there will be a power failure during the conversion, our RAID volume will resync during boot,
            // To prevent destruction of the data, we temporarily convert the array to striped volume
            VolumeManagerDatabaseHelper.ConvertRaidToStripedVolume(database, volume.VolumeGuid);
            ulong newExtentID = VolumeManagerDatabaseHelper.AddNewExtentToVolume(database, volume, newExtent);

            // Backup the first sector of the first extent to the last sector of the new extent
            // (We replace the filesystem boot record with our own sector for recovery purposes)
            byte[] filesystemBootRecord = volume.Extents[0].ReadSector(0);
            newExtent.WriteSectors(newExtent.TotalSectors - 1, filesystemBootRecord);

            AddDiskOperationResumeRecord resumeRecord = new AddDiskOperationResumeRecord();

            resumeRecord.VolumeGuid = volume.VolumeGuid;
            PrivateHeader privateHeader = PrivateHeader.ReadFromDisk(newExtent.Disk);

            // privateHeader cannot be null at this point
            resumeRecord.NumberOfCommittedSectors = 0;

            // we use volume.WriteSectors so that the parity information will be update
            // this way, we could recover the first sector of each extent if a disk will fail
            volume.WriteSectors(0, resumeRecord.GetBytes(volume.BytesPerSector));

            ResumeAddDiskToRaid5Volume(database, volume, new DynamicDiskExtent(newExtent, newExtentID), resumeRecord, ref bytesCopied);
        }
        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 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 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);
        }
Пример #5
0
 public static void ExtendDynamicVolume(DynamicVolume volume, long numberOfAdditionalExtentSectors, DiskGroupDatabase database)
 {
     if (volume is SimpleVolume)
     {
         SimpleVolume simpleVolume = (SimpleVolume)volume;
         VolumeManagerDatabaseHelper.ExtendSimpleVolume(database, simpleVolume, numberOfAdditionalExtentSectors);
     }
     else if (volume is StripedVolume)
     {
         StripedVolume stripedVolume = (StripedVolume)volume;
         VolumeManagerDatabaseHelper.ExtendStripedVolume(database, stripedVolume, numberOfAdditionalExtentSectors);
     }
     else if (volume is Raid5Volume)
     {
         Raid5Volume raid5Volume = (Raid5Volume)volume;
         VolumeManagerDatabaseHelper.ExtendRAID5Volume(database, raid5Volume, numberOfAdditionalExtentSectors);
     }
 }
        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);
            }
        }
Пример #8
0
        private void btnOK_Click(object sender, EventArgs e)
        {
            int diskCount = listDisks.CheckedIndices.Count;

            if (rbSimple.Checked && diskCount != 1)
            {
                MessageBox.Show("You must select a single disk in order to create a simple volume.", "Error");
                return;
            }

            if (rbRaid5.Checked)
            {
                if (chkDegraded.Checked && diskCount < 2)
                {
                    MessageBox.Show("You must select at least 2 disks in order to create a degraded RAID-5 volume.", "Error");
                    return;
                }

                if (!chkDegraded.Checked && diskCount < 3)
                {
                    MessageBox.Show("You must select at least 3 disks in order to create a RAID-5 volume.", "Error");
                    return;
                }
            }

            long extentSizeInBytes    = (long)numericExtentSize.Value * 1024 * 1024;
            List <DiskExtent> extents = new List <DiskExtent>();

            foreach (int checkedIndex in listDisks.CheckedIndices)
            {
                DynamicDisk dynamicDisk = m_diskGroup[checkedIndex];
                DiskExtent  extent      = DynamicDiskHelper.FindExtentAllocation(dynamicDisk, extentSizeInBytes);
                if (extent == null)
                {
                    MessageBox.Show("One of the disks does not contain enough free space", "Error");
                    return;
                }
                extents.Add(extent);
            }

            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)
            {
                DiskGroupDatabase database = DiskGroupDatabase.ReadFromDisks(m_diskGroup)[0];
                if (rbSimple.Checked)
                {
                    VolumeManagerDatabaseHelper.CreateSimpleVolume(database, extents[0]);
                }
                else
                {
                    VolumeManagerDatabaseHelper.CreateRAID5Volume(database, extents, chkDegraded.Checked);
                }

                DiskGroupHelper.UnlockDiskGroup(m_diskGroup);
                this.DialogResult = DialogResult.OK;
                this.Close();
            }
        }